1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/filesys.cpp
3 // Purpose: wxFileSystem class - interface for opening files
4 // Author: Vaclav Slavik
5 // Copyright: (c) 1999 Vaclav Slavik
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
10 #include "wx/wxprec.h"
19 #include "wx/filesys.h"
23 #include "wx/module.h"
26 #include "wx/sysopt.h"
27 #include "wx/wfstream.h"
28 #include "wx/mimetype.h"
29 #include "wx/filename.h"
30 #include "wx/tokenzr.h"
32 #include "wx/private/fileback.h"
34 // ----------------------------------------------------------------------------
36 // ----------------------------------------------------------------------------
38 const wxString
& wxFSFile::GetMimeType() const
40 if ( m_MimeType
.empty() && !m_Location
.empty() )
42 wxConstCast(this, wxFSFile
)->m_MimeType
=
43 wxFileSystemHandler::GetMimeTypeFromExt(m_Location
);
49 // ----------------------------------------------------------------------------
50 // wxFileSystemHandler
51 // ----------------------------------------------------------------------------
53 IMPLEMENT_ABSTRACT_CLASS(wxFileSystemHandler
, wxObject
)
57 wxString
wxFileSystemHandler::GetMimeTypeFromExt(const wxString
& location
)
60 wxString loc
= GetRightLocation(location
);
62 int l
= loc
.length(), l2
;
65 for (int i
= l
-1; i
>= 0; i
--)
67 c
= loc
[(unsigned int) i
];
72 ext
= loc
.Right(l2
-i
-1);
75 if ( (c
== wxT('/')) || (c
== wxT('\\')) || (c
== wxT(':')) )
80 static bool s_MinimalMimeEnsured
= false;
82 // Don't use mime types manager if the application doesn't need it and it would be
83 // cause an unacceptable delay, especially on startup.
84 #if wxUSE_SYSTEM_OPTIONS
85 if ( !wxSystemOptions::GetOptionInt(wxT("filesys.no-mimetypesmanager")) )
88 if (!s_MinimalMimeEnsured
)
90 static const wxFileTypeInfo fallbacks
[] =
92 wxFileTypeInfo(wxT("image/jpeg"),
95 wxT("JPEG image (from fallback)"),
96 wxT("jpg"), wxT("jpeg"), wxT("JPG"), wxT("JPEG"), wxNullPtr
),
97 wxFileTypeInfo(wxT("image/gif"),
100 wxT("GIF image (from fallback)"),
101 wxT("gif"), wxT("GIF"), wxNullPtr
),
102 wxFileTypeInfo(wxT("image/png"),
105 wxT("PNG image (from fallback)"),
106 wxT("png"), wxT("PNG"), wxNullPtr
),
107 wxFileTypeInfo(wxT("image/bmp"),
110 wxT("windows bitmap image (from fallback)"),
111 wxT("bmp"), wxT("BMP"), wxNullPtr
),
112 wxFileTypeInfo(wxT("text/html"),
115 wxT("HTML document (from fallback)"),
116 wxT("htm"), wxT("html"), wxT("HTM"), wxT("HTML"), wxNullPtr
),
117 // must terminate the table with this!
120 wxTheMimeTypesManager
->AddFallbacks(fallbacks
);
121 s_MinimalMimeEnsured
= true;
124 wxFileType
*ft
= wxTheMimeTypesManager
->GetFileTypeFromExtension(ext
);
125 if ( !ft
|| !ft
-> GetMimeType(&mime
) )
127 mime
= wxEmptyString
;
137 if ( ext
.IsSameAs(wxT("htm"), false) || ext
.IsSameAs(wxT("html"), false) )
138 return wxT("text/html");
139 if ( ext
.IsSameAs(wxT("jpg"), false) || ext
.IsSameAs(wxT("jpeg"), false) )
140 return wxT("image/jpeg");
141 if ( ext
.IsSameAs(wxT("gif"), false) )
142 return wxT("image/gif");
143 if ( ext
.IsSameAs(wxT("png"), false) )
144 return wxT("image/png");
145 if ( ext
.IsSameAs(wxT("bmp"), false) )
146 return wxT("image/bmp");
147 return wxEmptyString
;
154 wxString
wxFileSystemHandler::GetProtocol(const wxString
& location
)
156 wxString s
= wxEmptyString
;
157 int i
, l
= location
.length();
160 for (i
= l
-1; (i
>= 0) && ((location
[i
] != wxT('#')) || (!fnd
)); i
--) {
161 if ((location
[i
] == wxT(':')) && (i
!= 1 /*win: C:\path*/)) fnd
= true;
163 if (!fnd
) return wxT("file");
164 for (++i
; (i
< l
) && (location
[i
] != wxT(':')); i
++) s
<< location
[i
];
170 wxString
wxFileSystemHandler::GetLeftLocation(const wxString
& location
)
175 for (i
= location
.length()-1; i
>= 0; i
--) {
176 if ((location
[i
] == wxT(':')) && (i
!= 1 /*win: C:\path*/)) fnd
= true;
177 else if (fnd
&& (location
[i
] == wxT('#'))) return location
.Left(i
);
179 return wxEmptyString
;
183 wxString
wxFileSystemHandler::GetRightLocation(const wxString
& location
)
185 int i
, l
= location
.length();
190 ((location
[i
] != wxT(':')) || (i
== 1) || (location
[i
-2] == wxT(':')));
193 if (location
[i
] == wxT('#')) l2
= i
+ 1;
195 if (i
== 0) return wxEmptyString
;
196 else return location
.Mid(i
+ 1, l2
- i
- 2);
200 wxString
wxFileSystemHandler::GetAnchor(const wxString
& location
)
203 int l
= location
.length();
205 for (int i
= l
-1; i
>= 0; i
--) {
208 return location
.Right(l
-i
-1);
209 else if ((c
== wxT('/')) || (c
== wxT('\\')) || (c
== wxT(':')))
210 return wxEmptyString
;
212 return wxEmptyString
;
216 wxString
wxFileSystemHandler::FindFirst(const wxString
& WXUNUSED(spec
),
219 return wxEmptyString
;
222 wxString
wxFileSystemHandler::FindNext()
224 return wxEmptyString
;
227 //--------------------------------------------------------------------------------
229 //--------------------------------------------------------------------------------
232 wxString
wxLocalFSHandler::ms_root
;
234 bool wxLocalFSHandler::CanOpen(const wxString
& location
)
236 return GetProtocol(location
) == wxT("file");
239 wxFSFile
* wxLocalFSHandler::OpenFile(wxFileSystem
& WXUNUSED(fs
), const wxString
& location
)
241 // location has Unix path separators
242 wxString right
= GetRightLocation(location
);
243 wxFileName fn
= wxFileSystem::URLToFileName(right
);
244 wxString fullpath
= ms_root
+ fn
.GetFullPath();
246 if (!wxFileExists(fullpath
))
249 // we need to check whether we can really read from this file, otherwise
250 // wxFSFile is not going to work
252 wxFFileInputStream
*is
= new wxFFileInputStream(fullpath
);
254 wxFileInputStream
*is
= new wxFileInputStream(fullpath
);
256 #error One of wxUSE_FILE or wxUSE_FFILE must be set to 1 for wxFSHandler to work
264 return new wxFSFile(is
,
269 ,wxDateTime(wxFileModificationTime(fullpath
))
270 #endif // wxUSE_DATETIME
274 wxString
wxLocalFSHandler::FindFirst(const wxString
& spec
, int flags
)
276 wxFileName fn
= wxFileSystem::URLToFileName(GetRightLocation(spec
));
277 const wxString found
= wxFindFirstFile(ms_root
+ fn
.GetFullPath(), flags
);
280 return wxFileSystem::FileNameToURL(found
);
283 wxString
wxLocalFSHandler::FindNext()
285 const wxString found
= wxFindNextFile();
288 return wxFileSystem::FileNameToURL(found
);
293 //-----------------------------------------------------------------------------
295 //-----------------------------------------------------------------------------
297 IMPLEMENT_DYNAMIC_CLASS(wxFileSystem
, wxObject
)
298 IMPLEMENT_ABSTRACT_CLASS(wxFSFile
, wxObject
)
301 wxList
wxFileSystem::m_Handlers
;
304 wxFileSystem::~wxFileSystem()
306 WX_CLEAR_HASH_MAP(wxFSHandlerHash
, m_LocalHandlers
)
310 static wxString
MakeCorrectPath(const wxString
& path
)
317 for (i
= 0; i
< cnt
; i
++)
318 if (p
.GetChar(i
) == wxT('\\')) p
.GetWritableChar(i
) = wxT('/'); // Want to be windows-safe
320 if (p
.Left(2) == wxT("./")) { p
= p
.Mid(2); cnt
-= 2; }
322 if (cnt
< 3) return p
;
324 r
<< p
.GetChar(0) << p
.GetChar(1);
326 // skip trailing ../.., if any
327 for (i
= 2; i
< cnt
&& (p
.GetChar(i
) == wxT('/') || p
.GetChar(i
) == wxT('.')); i
++) r
<< p
.GetChar(i
);
329 // remove back references: translate dir1/../dir2 to dir2
333 if (p
.GetChar(i
) == wxT('/') && p
.GetChar(i
-1) == wxT('.') && p
.GetChar(i
-2) == wxT('.'))
335 for (j
= r
.length() - 2; j
>= 0 && r
.GetChar(j
) != wxT('/') && r
.GetChar(j
) != wxT(':'); j
--) {}
336 if (j
>= 0 && r
.GetChar(j
) != wxT(':'))
338 for (j
= j
- 1; j
>= 0 && r
.GetChar(j
) != wxT('/') && r
.GetChar(j
) != wxT(':'); j
--) {}
344 for (; i
< cnt
; i
++) r
<< p
.GetChar(i
);
350 void wxFileSystem::ChangePathTo(const wxString
& location
, bool is_dir
)
354 m_Path
= MakeCorrectPath(location
);
358 if (!m_Path
.empty() && m_Path
.Last() != wxT('/') && m_Path
.Last() != wxT(':'))
364 for (i
= m_Path
.length()-1; i
>= 0; i
--)
366 if (m_Path
[(unsigned int) i
] == wxT('/'))
368 if ((i
> 1) && (m_Path
[(unsigned int) (i
-1)] == wxT('/')) && (m_Path
[(unsigned int) (i
-2)] == wxT(':')))
379 else if (m_Path
[(unsigned int) i
] == wxT(':')) {
386 for (i
= 0; i
< (int) m_Path
.length(); i
++)
388 if (m_Path
[(unsigned int) i
] == wxT(':'))
394 if (i
== (int) m_Path
.length())
395 m_Path
= wxEmptyString
;
399 m_Path
.Remove(pathpos
+1);
406 wxFileSystemHandler
*wxFileSystem::MakeLocal(wxFileSystemHandler
*h
)
408 wxClassInfo
*classinfo
= h
->GetClassInfo();
410 if (classinfo
->IsDynamic())
412 wxFileSystemHandler
*& local
= m_LocalHandlers
[classinfo
];
414 local
= (wxFileSystemHandler
*)classinfo
->CreateObject();
425 wxFSFile
* wxFileSystem::OpenFile(const wxString
& location
, int flags
)
427 if ((flags
& wxFS_READ
) == 0)
430 wxString loc
= MakeCorrectPath(location
);
434 wxList::compatibility_iterator node
;
438 for (i
= 0; i
< ln
; i
++)
440 switch ( loc
[i
].GetValue() )
442 case wxT('/') : case wxT(':') : case wxT('#') :
446 if (meta
!= 0) break;
448 m_LastName
= wxEmptyString
;
450 // try relative paths first :
451 if (meta
!= wxT(':'))
453 node
= m_Handlers
.GetFirst();
456 wxFileSystemHandler
*h
= (wxFileSystemHandler
*) node
-> GetData();
457 if (h
->CanOpen(m_Path
+ loc
))
459 s
= MakeLocal(h
)->OpenFile(*this, m_Path
+ loc
);
460 if (s
) { m_LastName
= m_Path
+ loc
; break; }
462 node
= node
->GetNext();
466 // if failed, try absolute paths :
469 node
= m_Handlers
.GetFirst();
472 wxFileSystemHandler
*h
= (wxFileSystemHandler
*) node
->GetData();
475 s
= MakeLocal(h
)->OpenFile(*this, loc
);
476 if (s
) { m_LastName
= loc
; break; }
478 node
= node
->GetNext();
482 if (s
&& (flags
& wxFS_SEEKABLE
) != 0 && !s
->GetStream()->IsSeekable())
484 wxBackedInputStream
*stream
;
485 stream
= new wxBackedInputStream(s
->DetachStream());
486 stream
->FindLength();
487 s
->SetStream(stream
);
495 wxString
wxFileSystem::FindFirst(const wxString
& spec
, int flags
)
497 wxList::compatibility_iterator node
;
498 wxString
spec2(spec
);
500 m_FindFileHandler
= NULL
;
502 for (int i
= spec2
.length()-1; i
>= 0; i
--)
503 if (spec2
[(unsigned int) i
] == wxT('\\')) spec2
.GetWritableChar(i
) = wxT('/'); // Want to be windows-safe
505 node
= m_Handlers
.GetFirst();
508 wxFileSystemHandler
*h
= (wxFileSystemHandler
*) node
-> GetData();
509 if (h
-> CanOpen(m_Path
+ spec2
))
511 m_FindFileHandler
= MakeLocal(h
);
512 return m_FindFileHandler
-> FindFirst(m_Path
+ spec2
, flags
);
514 node
= node
->GetNext();
517 node
= m_Handlers
.GetFirst();
520 wxFileSystemHandler
*h
= (wxFileSystemHandler
*) node
-> GetData();
521 if (h
-> CanOpen(spec2
))
523 m_FindFileHandler
= MakeLocal(h
);
524 return m_FindFileHandler
-> FindFirst(spec2
, flags
);
526 node
= node
->GetNext();
529 return wxEmptyString
;
534 wxString
wxFileSystem::FindNext()
536 if (m_FindFileHandler
== NULL
) return wxEmptyString
;
537 else return m_FindFileHandler
-> FindNext();
540 bool wxFileSystem::FindFileInPath(wxString
*pStr
,
541 const wxString
& path
,
542 const wxString
& basename
)
544 // we assume that it's not empty
545 wxCHECK_MSG( !basename
.empty(), false,
546 wxT("empty file name in wxFileSystem::FindFileInPath"));
549 // skip path separator in the beginning of the file name if present
550 if ( wxIsPathSeparator(basename
[0u]) )
551 name
= basename
.substr(1);
555 wxStringTokenizer
tokenizer(path
, wxPATH_SEP
);
556 while ( tokenizer
.HasMoreTokens() )
558 wxString strFile
= tokenizer
.GetNextToken();
559 if ( !wxEndsWithPathSeparator(strFile
) )
560 strFile
+= wxFILE_SEP_PATH
;
563 wxFSFile
*file
= OpenFile(strFile
);
575 void wxFileSystem::AddHandler(wxFileSystemHandler
*handler
)
577 // prepend the handler to the beginning of the list because handlers added
578 // last should have the highest priority to allow overriding them
579 m_Handlers
.Insert((size_t)0, handler
);
582 wxFileSystemHandler
* wxFileSystem::RemoveHandler(wxFileSystemHandler
*handler
)
584 // if handler has already been removed (or deleted)
585 // we return NULL. This is by design in case
586 // CleanUpHandlers() is called before RemoveHandler
587 // is called, as we cannot control the order
588 // which modules are unloaded
589 if (!m_Handlers
.DeleteObject(handler
))
596 bool wxFileSystem::HasHandlerForPath(const wxString
&location
)
598 for ( wxList::compatibility_iterator node
= m_Handlers
.GetFirst();
599 node
; node
= node
->GetNext() )
601 wxFileSystemHandler
*h
= (wxFileSystemHandler
*) node
->GetData();
602 if (h
->CanOpen(location
))
609 void wxFileSystem::CleanUpHandlers()
611 WX_CLEAR_LIST(wxList
, m_Handlers
);
614 static const wxString
g_unixPathString(wxT("/"));
615 static const wxString
g_nativePathString(wxFILE_SEP_PATH
);
617 // Returns the native path for a file URL
618 wxFileName
wxFileSystem::URLToFileName(const wxString
& url
)
622 if ( path
.Find(wxT("file://")) == 0 )
626 else if ( path
.Find(wxT("file:")) == 0 )
630 // Remove preceding double slash on Mac Classic
631 #if defined(__WXMAC__) && !defined(__UNIX__)
632 else if ( path
.Find(wxT("//")) == 0 )
636 path
= wxURI::Unescape(path
);
639 // file urls either start with a forward slash (local harddisk),
640 // otherwise they have a servername/sharename notation,
641 // which only exists on msw and corresponds to a unc
642 if ( path
.length() > 1 && (path
[0u] == wxT('/') && path
[1u] != wxT('/')) )
646 else if ( (url
.Find(wxT("file://")) == 0) &&
647 (path
.Find(wxT('/')) != wxNOT_FOUND
) &&
648 (path
.length() > 1) && (path
[1u] != wxT(':')) )
650 path
= wxT("//") + path
;
654 path
.Replace(g_unixPathString
, g_nativePathString
);
656 return wxFileName(path
, wxPATH_NATIVE
);
659 // Escapes non-ASCII and others characters in file: URL to be valid URLs
660 static wxString
EscapeFileNameCharsInURL(const char *in
)
664 for ( const unsigned char *p
= (const unsigned char*)in
; *p
; ++p
)
666 const unsigned char c
= *p
;
668 // notice that all colons *must* be encoded in the paths used by
669 // wxFileSystem even though this makes URLs produced by this method
670 // unusable with IE under Windows as it requires "file:///c:/foo.bar"
671 // and doesn't accept "file:///c%3a/foo.bar" -- but then we never made
672 // any guarantees about general suitability of the strings returned by
673 // this method, they must work with wxFileSystem only and not encoding
674 // the colon breaks handling of
675 // "http://wherever/whatever.zip#zip:filename.ext" URLs so we really
676 // can't do this without heavy changes to the parsing code here, in
677 // particular in GetRightLocation()
679 if ( c
== '/' || c
== '-' || c
== '.' || c
== '_' || c
== '~' ||
680 (c
>= '0' && c
<= '9') ||
681 (c
>= 'a' && c
<= 'z') ||
682 (c
>= 'A' && c
<= 'Z') )
688 s
<< wxString::Format("%%%02x", c
);
695 // Returns the file URL for a native path
696 wxString
wxFileSystem::FileNameToURL(const wxFileName
& filename
)
698 wxFileName fn
= filename
;
699 fn
.Normalize(wxPATH_NORM_DOTS
| wxPATH_NORM_TILDE
| wxPATH_NORM_ABSOLUTE
);
700 wxString url
= fn
.GetFullPath(wxPATH_NATIVE
);
703 // unc notation, wxMSW
704 if ( url
.Find(wxT("\\\\")) == 0 )
706 url
= wxT("//") + url
.Mid(2);
710 url
= wxT("/") + url
;
712 url
= wxT("/") + url
;
718 url
.Replace(g_nativePathString
, g_unixPathString
);
720 // Do wxURI- and common practice-compatible escaping: encode the string
721 // into UTF-8, then escape anything non-ASCII:
722 return wxT("file:") + EscapeFileNameCharsInURL(url
.utf8_str());
728 class wxFileSystemModule
: public wxModule
730 DECLARE_DYNAMIC_CLASS(wxFileSystemModule
)
733 wxFileSystemModule() :
739 virtual bool OnInit()
741 m_handler
= new wxLocalFSHandler
;
742 wxFileSystem::AddHandler(m_handler
);
745 virtual void OnExit()
747 delete wxFileSystem::RemoveHandler(m_handler
);
749 wxFileSystem::CleanUpHandlers();
753 wxFileSystemHandler
* m_handler
;
757 IMPLEMENT_DYNAMIC_CLASS(wxFileSystemModule
, wxModule
)
761 wxFSInputStream::wxFSInputStream(const wxString
& filename
, int flags
)
764 m_file
= fs
.OpenFile(filename
, flags
| wxFS_READ
);
768 wxInputStream
* const stream
= m_file
->GetStream();
771 // Notice that we pass the stream by reference: it shouldn't be
772 // deleted by us as it's owned by m_file already.
773 InitParentStream(*stream
);
778 wxFSInputStream::~wxFSInputStream()