1/* This file is part of the KDE libraries
2 Copyright (C) 2000 David Faure <faure@kde.org>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library 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 GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
18*/
19
20#include "kio_archive.h"
21
22#include <sys/types.h>
23#include <sys/stat.h>
24#include <stdlib.h>
25#include <unistd.h>
26
27#include <QFile>
28
29#include <kglobal.h>
30#include <kurl.h>
31#include <kdebug.h>
32#include <kcomponentdata.h>
33#include <ktar.h>
34#include <kzip.h>
35#include <kar.h>
36#include <kmimetype.h>
37#include <klocale.h>
38#include <kde_file.h>
39#include <kio/global.h>
40
41#include <kuser.h>
42
43using namespace KIO;
44
45extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); }
46
47int kdemain( int argc, char **argv )
48{
49 KComponentData componentData( "kio_archive" );
50
51 kDebug(7109) << "Starting" << getpid();
52
53 if (argc != 4)
54 {
55 fprintf(stderr, "Usage: kio_archive protocol domain-socket1 domain-socket2\n");
56 exit(-1);
57 }
58
59 ArchiveProtocol slave(argv[2], argv[3]);
60 slave.dispatchLoop();
61
62 kDebug(7109) << "Done";
63 return 0;
64}
65
66ArchiveProtocol::ArchiveProtocol( const QByteArray &pool, const QByteArray &app ) : SlaveBase( "tar", pool, app )
67{
68 kDebug( 7109 ) << "ArchiveProtocol::ArchiveProtocol";
69 m_archiveFile = 0L;
70}
71
72ArchiveProtocol::~ArchiveProtocol()
73{
74 delete m_archiveFile;
75}
76
77bool ArchiveProtocol::checkNewFile( const KUrl & url, QString & path, KIO::Error& errorNum )
78{
79#ifndef Q_WS_WIN
80 QString fullPath = url.path();
81#else
82 QString fullPath = url.path().remove(0, 1);
83#endif
84 kDebug(7109) << "ArchiveProtocol::checkNewFile" << fullPath;
85
86
87 // Are we already looking at that file ?
88 if ( m_archiveFile && m_archiveName == fullPath.left(m_archiveName.length()) )
89 {
90 // Has it changed ?
91 KDE_struct_stat statbuf;
92 if ( KDE_stat( QFile::encodeName( m_archiveName ), &statbuf ) == 0 )
93 {
94 if ( m_mtime == statbuf.st_mtime )
95 {
96 path = fullPath.mid( m_archiveName.length() );
97 kDebug(7109) << "ArchiveProtocol::checkNewFile returning" << path;
98 return true;
99 }
100 }
101 }
102 kDebug(7109) << "Need to open a new file";
103
104 // Close previous file
105 if ( m_archiveFile )
106 {
107 m_archiveFile->close();
108 delete m_archiveFile;
109 m_archiveFile = 0L;
110 }
111
112 // Find where the tar file is in the full path
113 int pos = 0;
114 QString archiveFile;
115 path.clear();
116
117 int len = fullPath.length();
118 if ( len != 0 && fullPath[ len - 1 ] != '/' )
119 fullPath += '/';
120
121 kDebug(7109) << "the full path is" << fullPath;
122 KDE_struct_stat statbuf;
123 statbuf.st_mode = 0; // be sure to clear the directory bit
124 while ( (pos=fullPath.indexOf( '/', pos+1 )) != -1 )
125 {
126 QString tryPath = fullPath.left( pos );
127 kDebug(7109) << fullPath << "trying" << tryPath;
128 if ( KDE_stat( QFile::encodeName(tryPath), &statbuf ) == -1 )
129 {
130 // We are not in the file system anymore, either we have already enough data or we will never get any useful data anymore
131 break;
132 }
133 if ( !S_ISDIR(statbuf.st_mode) )
134 {
135 archiveFile = tryPath;
136 m_mtime = statbuf.st_mtime;
137#ifdef Q_WS_WIN // st_uid and st_gid provides no information
138 m_user.clear();
139 m_group.clear();
140#else
141 KUser user(statbuf.st_uid);
142 m_user = user.loginName();
143 KUserGroup group(statbuf.st_gid);
144 m_group = group.name();
145#endif
146 path = fullPath.mid( pos + 1 );
147 kDebug(7109).nospace() << "fullPath=" << fullPath << " path=" << path;
148 len = path.length();
149 if ( len > 1 )
150 {
151 if ( path[ len - 1 ] == '/' )
152 path.truncate( len - 1 );
153 }
154 else
155 path = QString::fromLatin1("/");
156 kDebug(7109).nospace() << "Found. archiveFile=" << archiveFile << " path=" << path;
157 break;
158 }
159 }
160 if ( archiveFile.isEmpty() )
161 {
162 kDebug(7109) << "ArchiveProtocol::checkNewFile: not found";
163 if ( S_ISDIR(statbuf.st_mode) ) // Was the last stat about a directory?
164 {
165 // Too bad, it is a directory, not an archive.
166 kDebug(7109) << "Path is a directory, not an archive.";
167 errorNum = KIO::ERR_IS_DIRECTORY;
168 }
169 else
170 errorNum = KIO::ERR_DOES_NOT_EXIST;
171 return false;
172 }
173
174 // Open new file
175 if ( url.protocol() == "tar" ) {
176 kDebug(7109) << "Opening KTar on" << archiveFile;
177 m_archiveFile = new KTar( archiveFile );
178 } else if ( url.protocol() == "ar" ) {
179 kDebug(7109) << "Opening KAr on " << archiveFile;
180 m_archiveFile = new KAr( archiveFile );
181 } else if ( url.protocol() == "zip" ) {
182 kDebug(7109) << "Opening KZip on " << archiveFile;
183 m_archiveFile = new KZip( archiveFile );
184 } else {
185 kWarning(7109) << "Protocol" << url.protocol() << "not supported by this IOSlave" ;
186 errorNum = KIO::ERR_UNSUPPORTED_PROTOCOL;
187 return false;
188 }
189
190 if ( !m_archiveFile->open( QIODevice::ReadOnly ) )
191 {
192 kDebug(7109) << "Opening" << archiveFile << "failed.";
193 delete m_archiveFile;
194 m_archiveFile = 0L;
195 errorNum = KIO::ERR_CANNOT_OPEN_FOR_READING;
196 return false;
197 }
198
199 m_archiveName = archiveFile;
200 return true;
201}
202
203
204void ArchiveProtocol::createRootUDSEntry( KIO::UDSEntry & entry )
205{
206 entry.clear();
207 entry.insert( KIO::UDSEntry::UDS_NAME, "." );
208 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR );
209 entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, m_mtime );
210 //entry.insert( KIO::UDSEntry::UDS_ACCESS, 07777 ); // fake 'x' permissions, this is a pseudo-directory
211 entry.insert( KIO::UDSEntry::UDS_USER, m_user);
212 entry.insert( KIO::UDSEntry::UDS_GROUP, m_group);
213}
214
215void ArchiveProtocol::createUDSEntry( const KArchiveEntry * archiveEntry, UDSEntry & entry )
216{
217 entry.clear();
218 entry.insert( KIO::UDSEntry::UDS_NAME, archiveEntry->name() );
219 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, archiveEntry->permissions() & S_IFMT ); // keep file type only
220 entry.insert( KIO::UDSEntry::UDS_SIZE, archiveEntry->isFile() ? ((KArchiveFile *)archiveEntry)->size() : 0L );
221 entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, archiveEntry->date());
222 entry.insert( KIO::UDSEntry::UDS_ACCESS, archiveEntry->permissions() & 07777 ); // keep permissions only
223 entry.insert( KIO::UDSEntry::UDS_USER, archiveEntry->user());
224 entry.insert( KIO::UDSEntry::UDS_GROUP, archiveEntry->group());
225 entry.insert( KIO::UDSEntry::UDS_LINK_DEST, archiveEntry->symLinkTarget());
226}
227
228void ArchiveProtocol::listDir( const KUrl & url )
229{
230 kDebug( 7109 ) << "ArchiveProtocol::listDir" << url.url();
231
232 QString path;
233 KIO::Error errorNum;
234 if ( !checkNewFile( url, path, errorNum ) )
235 {
236 if ( errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING )
237 {
238 // If we cannot open, it might be a problem with the archive header (e.g. unsupported format)
239 // Therefore give a more specific error message
240 error( KIO::ERR_SLAVE_DEFINED,
241 i18n( "Could not open the file, probably due to an unsupported file format.\n%1",
242 url.prettyUrl() ) );
243 return;
244 }
245 else if ( errorNum != ERR_IS_DIRECTORY )
246 {
247 // We have any other error
248 error( errorNum, url.prettyUrl() );
249 return;
250 }
251 // It's a real dir -> redirect
252 KUrl redir;
253 redir.setPath( url.path() );
254 kDebug( 7109 ) << "Ok, redirection to" << redir.url();
255 redirection( redir );
256 finished();
257 // And let go of the tar file - for people who want to unmount a cdrom after that
258 delete m_archiveFile;
259 m_archiveFile = 0L;
260 return;
261 }
262
263 if ( path.isEmpty() )
264 {
265 KUrl redir( url.protocol() + QString::fromLatin1( ":/") );
266 kDebug( 7109 ) << "url.path()=" << url.path();
267 redir.setPath( url.path() + QString::fromLatin1("/") );
268 kDebug( 7109 ) << "ArchiveProtocol::listDir: redirection" << redir.url();
269 redirection( redir );
270 finished();
271 return;
272 }
273
274 kDebug( 7109 ) << "checkNewFile done";
275 const KArchiveDirectory* root = m_archiveFile->directory();
276 const KArchiveDirectory* dir;
277 if (!path.isEmpty() && path != "/")
278 {
279 kDebug(7109) << "Looking for entry" << path;
280 const KArchiveEntry* e = root->entry( path );
281 if ( !e )
282 {
283 error( KIO::ERR_DOES_NOT_EXIST, url.prettyUrl() );
284 return;
285 }
286 if ( ! e->isDirectory() )
287 {
288 error( KIO::ERR_IS_FILE, url.prettyUrl() );
289 return;
290 }
291 dir = (KArchiveDirectory*)e;
292 } else {
293 dir = root;
294 }
295
296 const QStringList l = dir->entries();
297 totalSize( l.count() );
298
299 UDSEntry entry;
300 if (!l.contains(".")) {
301 createRootUDSEntry(entry);
302 listEntry(entry, false);
303 }
304
305 QStringList::const_iterator it = l.begin();
306 for( ; it != l.end(); ++it )
307 {
308 kDebug(7109) << (*it);
309 const KArchiveEntry* archiveEntry = dir->entry( (*it) );
310
311 createUDSEntry( archiveEntry, entry );
312
313 listEntry( entry, false );
314 }
315
316 listEntry( entry, true ); // ready
317
318 finished();
319
320 kDebug( 7109 ) << "ArchiveProtocol::listDir done";
321}
322
323void ArchiveProtocol::stat( const KUrl & url )
324{
325 QString path;
326 UDSEntry entry;
327 KIO::Error errorNum;
328 if ( !checkNewFile( url, path, errorNum ) )
329 {
330 // We may be looking at a real directory - this happens
331 // when pressing up after being in the root of an archive
332 if ( errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING )
333 {
334 // If we cannot open, it might be a problem with the archive header (e.g. unsupported format)
335 // Therefore give a more specific error message
336 error( KIO::ERR_SLAVE_DEFINED,
337 i18n( "Could not open the file, probably due to an unsupported file format.\n%1",
338 url.prettyUrl() ) );
339 return;
340 }
341 else if ( errorNum != ERR_IS_DIRECTORY )
342 {
343 // We have any other error
344 error( errorNum, url.prettyUrl() );
345 return;
346 }
347 // Real directory. Return just enough information for KRun to work
348 entry.insert( KIO::UDSEntry::UDS_NAME, url.fileName());
349 kDebug( 7109 ).nospace() << "ArchiveProtocol::stat returning name=" << url.fileName();
350
351 KDE_struct_stat buff;
352#ifdef Q_WS_WIN
353 QString fullPath = url.path().remove(0, 1);
354#else
355 QString fullPath = url.path();
356#endif
357
358 if ( KDE_stat( QFile::encodeName( fullPath ), &buff ) == -1 )
359 {
360 // Should not happen, as the file was already stated by checkNewFile
361 error( KIO::ERR_COULD_NOT_STAT, url.prettyUrl() );
362 return;
363 }
364
365 entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, buff.st_mode & S_IFMT);
366
367 statEntry( entry );
368
369 finished();
370
371 // And let go of the tar file - for people who want to unmount a cdrom after that
372 delete m_archiveFile;
373 m_archiveFile = 0L;
374 return;
375 }
376
377 const KArchiveDirectory* root = m_archiveFile->directory();
378 const KArchiveEntry* archiveEntry;
379 if ( path.isEmpty() )
380 {
381 path = QString::fromLatin1( "/" );
382 archiveEntry = root;
383 } else {
384 archiveEntry = root->entry( path );
385 }
386 if ( !archiveEntry )
387 {
388 error( KIO::ERR_DOES_NOT_EXIST, url.prettyUrl() );
389 return;
390 }
391
392 createUDSEntry( archiveEntry, entry );
393 statEntry( entry );
394
395 finished();
396}
397
398void ArchiveProtocol::get( const KUrl & url )
399{
400 kDebug( 7109 ) << "ArchiveProtocol::get" << url.url();
401
402 QString path;
403 KIO::Error errorNum;
404 if ( !checkNewFile( url, path, errorNum ) )
405 {
406 if ( errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING )
407 {
408 // If we cannot open, it might be a problem with the archive header (e.g. unsupported format)
409 // Therefore give a more specific error message
410 error( KIO::ERR_SLAVE_DEFINED,
411 i18n( "Could not open the file, probably due to an unsupported file format.\n%1",
412 url.prettyUrl() ) );
413 return;
414 }
415 else
416 {
417 // We have any other error
418 error( errorNum, url.prettyUrl() );
419 return;
420 }
421 }
422
423 const KArchiveDirectory* root = m_archiveFile->directory();
424 const KArchiveEntry* archiveEntry = root->entry( path );
425
426 if ( !archiveEntry )
427 {
428 error( KIO::ERR_DOES_NOT_EXIST, url.prettyUrl() );
429 return;
430 }
431 if ( archiveEntry->isDirectory() )
432 {
433 error( KIO::ERR_IS_DIRECTORY, url.prettyUrl() );
434 return;
435 }
436 const KArchiveFile* archiveFileEntry = static_cast<const KArchiveFile *>(archiveEntry);
437 if ( !archiveEntry->symLinkTarget().isEmpty() )
438 {
439 kDebug(7109) << "Redirection to" << archiveEntry->symLinkTarget();
440 KUrl realURL( url, archiveEntry->symLinkTarget() );
441 kDebug(7109).nospace() << "realURL=" << realURL.url();
442 redirection( realURL );
443 finished();
444 return;
445 }
446
447 //kDebug(7109) << "Preparing to get the archive data";
448
449 /*
450 * The easy way would be to get the data by calling archiveFileEntry->data()
451 * However this has drawbacks:
452 * - the complete file must be read into the memory
453 * - errors are skipped, resulting in an empty file
454 */
455
456 QIODevice* io = archiveFileEntry->createDevice();
457
458 if (!io)
459 {
460 error( KIO::ERR_SLAVE_DEFINED,
461 i18n( "The archive file could not be opened, perhaps because the format is unsupported.\n%1" ,
462 url.prettyUrl() ) );
463 return;
464 }
465
466 if ( !io->open( QIODevice::ReadOnly ) )
467 {
468 error( KIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyUrl() );
469 delete io;
470 return;
471 }
472
473 totalSize( archiveFileEntry->size() );
474
475 // Size of a QIODevice read. It must be large enough so that the mime type check will not fail
476 const qint64 maxSize = 0x100000; // 1MB
477
478 qint64 bufferSize = qMin( maxSize, archiveFileEntry->size() );
479 QByteArray buffer;
480 buffer.resize( bufferSize );
481 if ( buffer.isEmpty() && bufferSize > 0 )
482 {
483 // Something went wrong
484 error( KIO::ERR_OUT_OF_MEMORY, url.prettyUrl() );
485 delete io;
486 return;
487 }
488
489 bool firstRead = true;
490
491 // How much file do we still have to process?
492 qint64 fileSize = archiveFileEntry->size();
493 KIO::filesize_t processed = 0;
494
495 while ( !io->atEnd() && fileSize > 0 )
496 {
497 if ( !firstRead )
498 {
499 bufferSize = qMin( maxSize, fileSize );
500 buffer.resize( bufferSize );
501 }
502 const qint64 read = io->read( buffer.data(), buffer.size() ); // Avoid to use bufferSize here, in case something went wrong.
503 if ( read != bufferSize )
504 {
505 kWarning(7109) << "Read" << read << "bytes but expected" << bufferSize ;
506 error( KIO::ERR_COULD_NOT_READ, url.prettyUrl() );
507 delete io;
508 return;
509 }
510 if ( firstRead )
511 {
512 // We use the magic one the first data read
513 // (As magic detection is about fixed positions, we can be sure that it is enough data.)
514 KMimeType::Ptr mime = KMimeType::findByNameAndContent( path, buffer );
515 kDebug(7109) << "Emitting mimetype" << mime->name();
516 mimeType( mime->name() );
517 firstRead = false;
518 }
519 data( buffer );
520 processed += read;
521 processedSize( processed );
522 fileSize -= bufferSize;
523 }
524 io->close();
525 delete io;
526
527 data( QByteArray() );
528
529 finished();
530}
531
532/*
533 In case someone wonders how the old filter stuff looked like : :)
534void TARProtocol::slotData(void *_p, int _len)
535{
536 switch (m_cmd) {
537 case CMD_PUT:
538 assert(m_pFilter);
539 m_pFilter->send(_p, _len);
540 break;
541 default:
542 abort();
543 break;
544 }
545}
546
547void TARProtocol::slotDataEnd()
548{
549 switch (m_cmd) {
550 case CMD_PUT:
551 assert(m_pFilter && m_pJob);
552 m_pFilter->finish();
553 m_pJob->dataEnd();
554 m_cmd = CMD_NONE;
555 break;
556 default:
557 abort();
558 break;
559 }
560}
561
562void TARProtocol::jobData(void *_p, int _len)
563{
564 switch (m_cmd) {
565 case CMD_GET:
566 assert(m_pFilter);
567 m_pFilter->send(_p, _len);
568 break;
569 case CMD_COPY:
570 assert(m_pFilter);
571 m_pFilter->send(_p, _len);
572 break;
573 default:
574 abort();
575 }
576}
577
578void TARProtocol::jobDataEnd()
579{
580 switch (m_cmd) {
581 case CMD_GET:
582 assert(m_pFilter);
583 m_pFilter->finish();
584 dataEnd();
585 break;
586 case CMD_COPY:
587 assert(m_pFilter);
588 m_pFilter->finish();
589 m_pJob->dataEnd();
590 break;
591 default:
592 abort();
593 }
594}
595
596void TARProtocol::filterData(void *_p, int _len)
597{
598debug("void TARProtocol::filterData");
599 switch (m_cmd) {
600 case CMD_GET:
601 data(_p, _len);
602 break;
603 case CMD_PUT:
604 assert (m_pJob);
605 m_pJob->data(_p, _len);
606 break;
607 case CMD_COPY:
608 assert(m_pJob);
609 m_pJob->data(_p, _len);
610 break;
611 default:
612 abort();
613 }
614}
615*/
616
617// kate: space-indent on; indent-width 4; replace-tabs on;
618