1 /*
2  *  Copyright (C) 2009-2014  Christian Heckendorf <heckendorfc@gmail.com>
3  *
4  *  This program is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 3 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "util.h"
19 #include "defs.h"
20 #include "dbact.h"
21 #include "plugins/plugin.h"
22 
experr(const char * epath,int eerrno)23 int experr(const char *epath, int eerrno){
24 	fprintf(stderr,"error: %d on %s\n",eerrno,epath);
25 	return 0;
26 }
27 
isURL(const char * in)28 int isURL(const char *in){
29 	return strncmp("http://",in,7)==0;
30 }
31 
expand(char * in)32 char *expand(char *in){
33 	char tmp[250];
34 	char *in_ptr=in-1;
35 	char *tmp_ptr=tmp;
36 	int url=0,x;
37 
38 	if(isURL(in))url=1; /* Clean \n but do no globbing */
39 
40 	for(x=0;x<249 && in[x];x++);
41 	if(x>0 && in[x-1]=='\n')
42 		in[x-1]=0;
43 	if(x>1 && in[x-2]=='\r')
44 		in[x-2]=0;
45 	in[x]=0;
46 
47 	if(url)return in;
48 
49 	if(in[0]=='~'){
50 		strcpy(tmp,getenv("HOME"));
51 		strcat(tmp,"/");
52 		strcat(tmp,in+2);
53 		strcpy(in,tmp);
54 	}
55 	else if(in[0]!='/'){
56 		strcpy(tmp,getenv("PWD"));
57 		strcat(tmp,"/");
58 		if(in[0]=='.' && in[1]=='/')
59 			strcat(tmp,in+2);
60 		else
61 			strcat(tmp,in);
62 		strcpy(in,tmp);
63 	}
64 	while(*(++in_ptr)){
65 		if(*in_ptr=='[' || *in_ptr=='\'' || *in_ptr=='\"')
66 			*(tmp_ptr++)='\\';
67 		*(tmp_ptr++)=*in_ptr;
68 	}
69 	*tmp_ptr=0;
70 
71 
72 	glob_t pglob;
73 	glob(tmp,GLOB_TILDE,&experr,&pglob);
74 	if(pglob.gl_pathc>0){
75 		memmove(tmp,pglob.gl_pathv[0],100);
76 		debug(1,"Glob OK!");
77 	}
78 	else
79 		debug(1,"No files match");
80 	globfree(&pglob);
81 	return in;
82 }
83 
fileFormat(struct pluginitem ** list,const char * arg)84 int fileFormat(struct pluginitem **list, const char *arg){
85 	FILE *ffd=NULL;
86 	struct pluginitem **ret=list;
87 	int type=PLUGIN_NULL;
88 	int x=0;
89 	int i,j;
90 
91 	// Find by magic numbers
92 	for(i=0;type==PLUGIN_NULL && i<PLUGIN_NULL;i++){
93 		if(ret[i]==NULL)
94 			continue;
95 
96 		if((ffd=ret[i]->modopen(arg,"rb"))!=NULL){
97 			if(ret[i]->moddata(ffd)){
98 				type=i;
99 			}
100 			ret[i]->modclose(ffd);
101 		}
102 	}
103 	if(ffd==NULL)
104 		type=PLUGIN_NULL;
105 
106 	if(type!=PLUGIN_NULL){
107 		return type;
108 	}
109 
110 	// Find by extension
111 	for(x=0;arg[x];x++);
112 	for(;arg[x-1]!='.' && x>0;x--);
113 	for(i=0;i<PLUGIN_NULL;i++){
114 		if(ret[i]==NULL)
115 			continue;
116 
117 		for(j=0;ret[i]->extension[j];j++)
118 			if(strcasecmp(arg+x,ret[i]->extension[j])==0)
119 				return i;
120 	}
121 
122 	return PLUGIN_NULL;
123 }
124 
125 #if 0
126 int getFileTypeByName(const char *name){
127 	char query[200];
128 	int type=0;
129 
130 	if(!name)return 0;
131 
132 	sprintf(query,"SELECT TypeID FROM FileType WHERE Name='%s' LIMIT 1",name);
133 	harp_sqlite3_exec(conn,query,uint_return_cb,&type,NULL);
134 
135 	return type;
136 }
137 
138 int findPluginIDByType(int type){
139 	static int last_type=0;
140 	static int index=0;
141 	unsigned int id=0;
142 	char query[150];
143 
144 	if(type==0 || (type!=last_type && last_type!=0)){
145 		last_type=index=0;
146 		if(type==0)return 0;
147 	}
148 	last_type=type;
149 
150 	sprintf(query,"SELECT PluginID FROM PluginType WHERE TypeID=%d LIMIT %d,1",type,index);
151 
152 	harp_sqlite3_exec(conn,query,uint_return_cb,&id,NULL);
153 	index++;
154 
155 	return id;
156 }
157 
158 struct pluginitem *findPluginByID(struct pluginitem *list, int id){
159 	while(list && list->id!=id)
160 		list=list->next;
161 	return list;
162 }
163 
164 int getPluginModule(void **module, char *lib){
165 	char library[250];
166 
167 	sprintf(library,"%s/%s",LIB_PATH,lib);
168 	debug(2,library);
169 	dlerror();
170 
171 	*module=dlopen(library,RTLD_LAZY);
172 	if(!*module){
173 		fprintf(stderr,"Can't open plugin.\n\t%s\n",dlerror());
174 		return 2;
175 	}
176 	else{
177 		dlerror();
178 	}
179 
180 	return 1;
181 }
182 
183 int getPlugin(struct dbitem *dbi, const int index, void **module){
184 	if(!fetch_row(dbi)){
185 		if(dbi->current_row==dbi->column_count || dbi->row_count==0)
186 			fprintf(stderr,"No plugins found.\n\tSee README for information about installing plugins.\n");
187 		return 0;
188 	}
189 
190 	return getPluginModule(module,dbi->row[index]);
191 }
192 
193 struct pluginitem *closePlugin(struct pluginitem *head){
194 	struct pluginitem *ptr=head;
195 
196 	if(!head)return NULL;
197 
198 	ptr=ptr->next;
199 	if(head->module)
200 		dlclose(head->module);
201 	free(head);
202 
203 	return ptr;
204 }
205 
206 void closePluginList(struct pluginitem *head){
207 	struct pluginitem *ptr=head;
208 
209 	do{
210 		ptr=closePlugin(ptr);
211 	}while(ptr);
212 }
213 
214 static int regPluginFunctions(struct pluginitem *node){
215 	if(!node->module)return 1;
216 
217 	if(!(node->modopen=dlsym(node->module,"plugin_open")) ||
218 		!(node->moddata=dlsym(node->module,"filetype_by_data")) ||
219 		!(node->modmeta=dlsym(node->module,"plugin_meta")) ||
220 		!(node->modplay=dlsym(node->module,"plugin_run")) ||
221 		!(node->modseek=dlsym(node->module,"plugin_seek")) ||
222 		!(node->modclose=dlsym(node->module,"plugin_close")))
223 		return 1;
224 
225 	return 0;
226 }
227 
228 static int open_plugin_cb(void *arg, int col_count, char **row, char **titles){
229 	struct pluginitem **node = (struct pluginitem**)arg;
230 	struct pluginitem *next;
231 
232 	if(!(next=malloc(sizeof(struct pluginitem)))){
233 		debug(2,"Malloc failed (open_plugin_cb).");
234 		return 1;
235 	}
236 	if(getPluginModule(&(next->module),row[1])!=1){
237 		free(next);
238 		return 1;
239 	}
240 	else{
241 		if(regPluginFunctions(next)){
242 			gree(nexu);
243 			return 1;
244 		}
245 		else{
246 			(*node)->next=next;
247 			*node=next;
248 			next->next=NULL;
249 			next->id=strtol(row[0],NULL,10);
250 			next->contenttype=strdup(row[2]);
251 		}
252 	}
253 
254 	return 0;
255 }
256 
257 struct pluginitem **openPlugins(){
258 	struct pluginitem prehead,*ptr;
259 
260 	return plugin_head;
261 
262 	prehead.next=NULL;
263 	ptr=&prehead;
264 	// Add order by active?
265 	if(harp_sqlite3_exec(conn,"SELECT Plugin.PluginID,Plugin.Library,FileType.ContentType FROM Plugin NATURAL JOIN PluginType NATURAL JOIN FileType",open_plugin_cb,&ptr,NULL)!=SQLITE_OK){
266 		closePluginList(prehead.next);
267 		fprintf(stderr,"Error opening plugins.\n");
268 		return NULL;
269 	}
270 
271 	//return prehead.next; /* List starts at this next */
272 }
273 #endif
274 
getFilename(const char * path)275 char *getFilename(const char *path){
276 	char *filestart=(char *)path;
277 	while(*path){
278 		if(*(path++)=='/')
279 			filestart=(char *)path;
280 	}
281 	return filestart;
282 }
283 
printIDMatchRow(struct dbitem * dbi)284 static void printIDMatchRow(struct dbitem *dbi){
285 	int x;
286 	printf("%s\t%s",dbi->row[0],dbi->row[1]);
287 	if(dbi->column_count>2){
288 		printf("\t(%s",dbi->row[2]);
289 		for(x=3;x<dbi->column_count;x++)
290 			printf(": %s",dbi->row[x]);
291 		printf(")");
292 	}
293 	printf("\n");
294 }
295 
getBestIDMatch(struct dbitem * dbi,const char * argv)296 int getBestIDMatch(struct dbitem *dbi, const char *argv){
297 	int id=0,found=0;
298 	if(!dbi)return 0;
299 
300 	printf("Total matches for string '%s': %d\n",argv,dbi->row_count);
301 	while(fetch_row(dbi)){
302 		printIDMatchRow(dbi);
303 		if(!strcmp(argv,dbi->row[1])){ // Perfect matches take priority
304 			id=(int)strtol(dbi->row[0],NULL,10);
305 			if(id)found++;
306 		}
307 		else if(!strcasecmp(argv,dbi->row[1])){  // Case-insensitive matches can be used
308 			id=(int)strtol(dbi->row[0],NULL,10); // Continue to look for perfect matches
309 		}
310 	}
311 	if(id && found==1)
312 		printf("Using best match: %d\n",id);
313 	else{
314 		char choice[100];
315 		printf("Please choose an ID: ");
316 		if(!fgets(choice,sizeof(choice),stdin))
317 			id=0;
318 		else
319 			id=(int)strtol(choice,NULL,10);
320 	}
321 	return id;
322 }
323 
strToID(const char * argv)324 int strToID(const char *argv){ // TODO: add type param
325 	char query[351];
326 	char clean_str[200];
327 	int id=0;
328 	struct dbitem dbi;
329 
330 	db_safe(clean_str,argv,200);
331 
332 	if(!arglist[ATYPE].subarg)return -1;
333 	dbiInit(&dbi);
334 
335 	switch(arglist[ATYPE].subarg[0]){
336 		case 's':sprintf(query,"SELECT SongID,SongTitle,ArtistName,AlbumTitle FROM SongPubInfo WHERE SongTitle LIKE '%%%s%%'",clean_str);break;
337 		case 'p':sprintf(query,"SELECT PlaylistID,Title FROM Playlist WHERE Title LIKE '%%%s%%'",clean_str);break;
338 		case 'r':sprintf(query,"SELECT ArtistID,Name FROM Artist WHERE Name LIKE '%%%s%%'",clean_str);break;
339 		case 'a':sprintf(query,"SELECT AlbumID,Title,Artist.Name FROM Album NATURAL JOIN AlbumArtist NATURAL JOIN Artist WHERE Title LIKE '%%%s%%'",clean_str);break;
340 		case 'g':sprintf(query,"SELECT CategoryID,Name FROM Category WHERE Name LIKE '%%%s%%'",clean_str);break;
341 		case 't':sprintf(query,"SELECT TagID,Value FROM Tag WHERE Value LIKE '%%%s%%'",clean_str);break;
342 		default:return -1;
343 	}
344 
345 	if(doQuery(query,&dbi)==1 && fetch_row(&dbi)){
346 		id=(int)strtol(dbi.row[0],NULL,10);
347 	}
348 	else if(dbi.row_count>1)
349 		id=getBestIDMatch(&dbi,argv);
350 
351 	dbiClean(&dbi);
352 	return id;
353 }
354 
verifyID(const int id)355 int verifyID(const int id){
356 	char query[100];
357 	struct dbitem dbi;
358 	dbiInit(&dbi);
359 	switch(arglist[ATYPE].subarg[0]){
360 		case 's':sprintf(query,"SELECT SongID FROM Song WHERE SongID=%d",id);break;
361 		case 'p':sprintf(query,"SELECT PlaylistID FROM Playlist WHERE PlaylistID=%d",id);break;
362 		case 'r':sprintf(query,"SELECT ArtistID FROM Artist WHERE ArtistID=%d",id);break;
363 		case 'a':sprintf(query,"SELECT AlbumID FROM Album WHERE AlbumID=%d",id);break;
364 		case 'g':sprintf(query,"SELECT CategoryID FROM Category WHERE CategoryID=%d",id);break;
365 		case 't':sprintf(query,"SELECT TagID FROM Tag WHERE TagID=%d",id);break;
366 		default:return 0;
367 	}
368 	if(doQuery(query,&dbi)==1){
369 		dbiClean(&dbi);
370 		return 1;
371 	}
372 	dbiClean(&dbi);
373 	return 0;
374 }
375 
getID(const char * arg)376 int getID(const char *arg){
377 	int id;
378 	char *endptr;
379 	if(arg==NULL){
380 		printf("Required argument not provided\n");
381 		return -1;
382 	}
383 	id=(int)strtol(arg,&endptr,10);
384 	if(*endptr!=0){
385 		if((id=strToID(arg))<1 ){
386 			debug(1,"No ID found from given name.");
387 			return -1;
388 		}
389 	}
390 	if(!verifyID(id)){
391 		debug(1,"No ID found.");
392 		return -1;
393 	}
394 	return id;
395 }
396 
getMulti(char * arg,int * length)397 int *getMulti(char *arg, int *length){
398 	const char del[]=",";
399 	char *token;
400 	int *list,*ptr;
401 
402 	*length=1;
403 	token=arg;
404 	while(*token){
405 		if(*(token++)==',')
406 			(*length)++;
407 	}
408 	if(!(ptr=list=malloc(sizeof(int)*(*length)))){
409 		debug(2,"Malloc failed (list).");
410 		return NULL;
411 	}
412 
413 	while((token=strsep(&arg,del))){
414 		if(!((*ptr=(int)strtol(token,NULL,10))>0 && verifyID(*ptr))){
415 			if(!(*ptr=strToID(token))){
416 				continue;
417 			}
418 		}
419 		ptr++;
420 	}
421 	if(ptr==list)*list=-1;
422 
423 	return list;
424 }
425 
getGroupSongIDs(char * args,const int arglen,struct IDList * id_struct)426 int getGroupSongIDs(char *args, const int arglen, struct IDList *id_struct){
427 	int y,x=0;
428 	int *song_ids=malloc(sizeof(int));
429 	int *group_ids=malloc(sizeof(int));
430 	int song_idlen;
431 	int group_idlen;
432 	char query[200],query_tail[100],*ptr;
433 	char temp=0;
434 	struct dbitem dbi;
435 	dbiInit(&dbi);
436 	query_tail[0]=0;
437 
438 	/* Default should be hit. Avoid selecting another option. */
439 	if(args[x+1]!=' '){
440 		temp=args[x];
441 		args[x]=' ';
442 	}
443 
444 	switch(args[x]){
445 		case 'a':
446 			arglist[ATYPE].subarg[0]='a';
447 			sprintf(query,"SELECT SongID FROM Song INNER JOIN Album ON Album.AlbumID=Song.AlbumID WHERE Album.AlbumID=");
448 			sprintf(query_tail,"ORDER BY Track");
449 			ptr=&query[91];
450 			break;
451 		case 'r':
452 			arglist[ATYPE].subarg[0]='r';
453 			sprintf(query,"SELECT SongID FROM Song NATURAL JOIN AlbumArtist WHERE ArtistID=");
454 			sprintf(query_tail,"ORDER BY Song.AlbumID,Track");
455 			ptr=&query[64];
456 			break;
457 		case 't':
458 			arglist[ATYPE].subarg[0]='t';
459 			sprintf(query,"SELECT Song.SongID FROM Song NATURAL JOIN SongTag WHERE TagID=");
460 			sprintf(query_tail,"ORDER BY Song.AlbumID,Track");
461 			ptr=&query[62];
462 			break;
463 		case 'g':
464 			arglist[ATYPE].subarg[0]='g';
465 			sprintf(query,"SELECT SongID FROM Song NATURAL JOIN SongCategory WHERE CategoryID=");
466 			sprintf(query_tail,"ORDER BY Song.AlbumID,Track");
467 			ptr=&query[67];
468 			break;
469 		case 's':
470 			x++;
471 		default:
472 			if(temp)args[x]=temp;
473 			// List of SongIDs.
474 			for(;x<arglen && args[x] && args[x]==' ';x++);
475 			arglist[ATYPE].subarg[0]='s';
476 			if(!args[x] || !(song_ids=getMulti(&args[x],&song_idlen)) || song_ids[0]<1)
477 				return HARP_RET_ERR;
478 			id_struct->tempselectid=insertTempSelect(song_ids,song_idlen);
479 			id_struct->songid=song_ids;
480 			id_struct->length=song_idlen;
481 			return HARP_RET_OK;
482 	}
483 
484 	debug(3,query);
485 	// Get SongIDs from album, artist, or genre
486 	for(++x;x<arglen && args[x] && args[x]==' ';x++);
487 	if(!(group_ids=getMulti(&args[x],&group_idlen)))return HARP_RET_ERR;
488 	if(group_ids[0]<1){
489 		fprintf(stderr,"No results found.\n");
490 		free(group_ids);
491 		return HARP_RET_ERR;
492 	}
493 	for(song_idlen=x=0;x<group_idlen;x++){
494 		sprintf(ptr,"%d %s",group_ids[x],query_tail);
495 		if(doQuery(query,&dbi)<1)continue;
496 		y=song_idlen;
497 		song_idlen+=dbi.row_count;
498 		if(!(song_ids=realloc(song_ids,sizeof(int)*(song_idlen)))){
499 			debug(2,"Realloc failed (song_ids).");
500 			dbiClean(&dbi);
501 			free(group_ids);
502 			return HARP_RET_ERR;
503 		}
504 		for(;fetch_row(&dbi);y++)
505 			song_ids[y]=(int)strtol(dbi.row[0],NULL,10);
506 	}
507 	dbiClean(&dbi);
508 	free(group_ids); // Done with groups. We have the SongIDs.
509 
510 	if(!song_idlen){
511 		fprintf(stderr,"No results found.\n");
512 		free(song_ids);
513 		return HARP_RET_ERR;
514 	}
515 	id_struct->tempselectid=insertTempSelect(song_ids,song_idlen);
516 	id_struct->songid=song_ids;
517 	id_struct->length=song_idlen;
518 
519 	return HARP_RET_OK;
520 }
521 
cleanTempSelect(const int tempid)522 void cleanTempSelect(const int tempid){
523 	char query[100];
524 	sprintf(query,"DELETE FROM TempSelect WHERE TempID=%d",tempid);
525 	harp_sqlite3_exec(conn,query,NULL,NULL,NULL);
526 }
527 
createTempSelect()528 static void createTempSelect(){
529 	harp_sqlite3_exec(conn,"CREATE TEMP TABLE IF NOT EXISTS TempSelect(TempSelectID integer not null primary key, TempID integer not null, SelectID integer not null)",NULL,NULL,NULL);
530 }
531 
getNextTempSelectID()532 static int getNextTempSelectID(){
533 	int tempid=0;
534 	/* Replace with MAX(TempID)? */
535 	if(harp_sqlite3_exec(conn,"SELECT TempID FROM TempSelect ORDER BY TempID DESC LIMIT 1",uint_return_cb,&tempid,NULL)==SQLITE_DONE)
536 		return 1;
537 	return ++tempid;
538 }
539 
mergeTempSelect(int ida,int idb)540 int mergeTempSelect(int ida, int idb){
541 	char query[150];
542 
543 	if(ida==idb)
544 		return ida;
545 
546 	sprintf(query,"UPDATE TempSelect SET TempID=%d where TempID=%d",ida,idb);
547 	harp_sqlite3_exec(conn,query,NULL,NULL,NULL);
548 
549 	return ida;
550 }
551 
insertTempSelect(const int * ids,const int idlen)552 int insertTempSelect(const int *ids, const int idlen){
553 	unsigned int x,currentlimit,tempid;
554 	struct dbitem dbi;
555 	dbiInit(&dbi);
556 	char query[150];
557 
558 	createTempSelect();
559 
560 	tempid=getNextTempSelectID();
561 
562 	x=currentlimit=0;
563 	while(x<idlen){
564 		if((currentlimit+=DB_BATCH_SIZE)>idlen)currentlimit=idlen;
565 		harp_sqlite3_exec(conn,"BEGIN",NULL,NULL,NULL);
566 		for(;x<currentlimit;x++){
567 			sprintf(query,"INSERT INTO TempSelect(TempID,SelectID) VALUES(%d,%d)",tempid,ids[x]);
568 			harp_sqlite3_exec(conn,query,NULL,NULL,NULL);
569 		}
570 		harp_sqlite3_exec(conn,"COMMIT",NULL,NULL,NULL);
571 	}
572 
573 	return tempid;
574 }
575 
insertTempSelectQuery(const char * query)576 int insertTempSelectQuery(const char *query){
577 	const char *ins_q="INSERT INTO TempSelect(TempID,SelectID) ";
578 	unsigned int tempid;
579 	char *temp_q,*ptr;
580 
581 	if(!(temp_q=malloc(strlen(ins_q)+strlen(query)+1))){
582 		debug(2,"Malloc failed (tempquery)");
583 		exit(1);
584 	}
585 
586 	createTempSelect();
587 
588 	tempid=getNextTempSelectID();
589 	sprintf(temp_q,query,tempid);
590 	ptr=strdup(temp_q);
591 	sprintf(temp_q,"%s%s",ins_q,ptr);
592 	debug(2,temp_q);
593 
594 	harp_sqlite3_exec(conn,temp_q,NULL,NULL,NULL);
595 
596 	free(temp_q);
597 	free(ptr);
598 	return tempid;
599 }
600 
insertTempSelectQueryCount(const char * query,int * count)601 int insertTempSelectQueryCount(const char *query,int *count){
602 	int ret=insertTempSelectQuery(query);
603 
604 	*count=sqlite3_changes(conn);
605 
606 	return ret;
607 }
608 
miClean(struct musicInfo * mi)609 void miClean(struct musicInfo *mi){
610 	memset(mi->title,0,MI_TITLE_SIZE);
611 	memset(mi->album,0,MI_ALBUM_SIZE);
612 	memset(mi->artist,0,MI_ARTIST_SIZE);
613 	memset(mi->year,0,MI_YEAR_SIZE);
614 	memset(mi->track,0,MI_TRACK_SIZE);
615 	mi->length=insertconf.length;
616 }
617 
miFree(struct musicInfo * mi)618 void miFree(struct musicInfo *mi){
619 	if(mi->title!=NULL)
620 		free(mi->title);
621 	if(mi->album!=NULL)
622 		free(mi->album);
623 	if(mi->artist!=NULL)
624 		free(mi->artist);
625 	if(mi->year!=NULL)
626 		free(mi->year);
627 	if(mi->track!=NULL)
628 		free(mi->track);
629 }
630 
db_clean(char * str,const char * data,const size_t size)631 void db_clean(char *str, const char *data, const size_t size){
632 	int x;
633 	for(x=0;*(data+x)==' ' && x<size;x++); // Strip starting spaces
634 	while(*data && x++<size){ // Strip multi space
635 		if(*data==' ' && *(data+1)==' '){
636 			data++;
637 			continue;
638 		}
639 		*(str++)=*(data++);
640 	}
641 	if(x>0 && *(str-1)==' ')str--; // Remove trailing space
642 	*str=0;
643 }
644 
db_safe(char * str,const char * data,const size_t size)645 void db_safe(char *str, const char *data, const size_t size){
646 	int x=0;
647 	while(*data && x++<size){ // Escape single quotes
648 		if(*data=='\''){
649 			*(str++)='\'';
650 		}
651 		*(str++)=*(data++);
652 	}
653 	*str=0;
654 }
655 
656