Improve comment
[amule.git] / src / SafeFile.cpp
blob61492bd6d09e71ac63aa73ef7307be41596c046e
1 //
2 // This file is part of the aMule Project.
3 //
4 // Copyright (c) 2003-2008 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002-2008 Merkur ( devs@emule-project.net / http://www.emule-project.net )
6 //
7 // Any parts of this program derived from the xMule, lMule or eMule project,
8 // or contributed by third-party developers are copyrighted by their
9 // respective authors.
11 // This program is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License as published by
13 // the Free Software Foundation; either version 2 of the License, or
14 // (at your option) any later version.
16 // This program is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 // GNU General Public License for more details.
20 //
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
26 #include "SafeFile.h" // Interface declarations.
27 #include "MD4Hash.h" // Needed for CMD4Hash
28 #include "kademlia/utils/UInt128.h" // Needed for CUInt128
29 #include "ScopedPtr.h" // Needed for CScopedPtr and CScopedArray
30 #include "Logger.h"
31 #include <common/Format.h> // Needed for CFormat
33 #if defined(__SUNPRO_CC)
34 #define __FUNCTION__ __FILE__+__LINE__
35 #endif
37 #define CHECK_BOM(size, x) ((size >= 3) && (x[0] == (char)0xEF) && (x[1] == (char)0xBB) && (x[2] == (char)0xBF))
39 const char BOMHeader[3] = { '\xEF', '\xBB', '\xBF'};
42 CSafeIOException::CSafeIOException(const wxString& type, const wxString& desc)
43 : CMuleException(wxT("SafeIO::") + type, desc) {}
46 CEOFException::CEOFException(const wxString& desc)
47 : CSafeIOException(wxT("EOF"), desc) {}
50 CIOFailureException::CIOFailureException(const wxString& desc)
51 : CSafeIOException(wxT("IOFailure"), desc) {}
53 CIOFailureException::CIOFailureException(const wxString& type, const wxString& desc)
54 : CSafeIOException(wxT("IOFailure::") + type, desc) {}
57 ///////////////////////////////////////////////////////////////////////////////
58 // CFileDataIO
61 CFileDataIO::~CFileDataIO()
66 bool CFileDataIO::Eof() const
68 return GetPosition() >= GetLength();
72 void CFileDataIO::Read(void *buffer, size_t count) const
74 MULE_VALIDATE_PARAMS(buffer, wxT("Attempting to write to NULL buffer."));
76 // Check that we read everything we wanted.
77 if (doRead(buffer, count) == (signed)count) {
78 return;
81 // To reduce potential system calls, we only do EOF checks when reads fail.
82 if (Eof()) {
83 throw CEOFException(wxT("Attempt to read past end of file."));
84 } else {
85 throw CIOFailureException(wxT("Read error, failed to read from file."));
90 void CFileDataIO::Write(const void* buffer, size_t count)
92 MULE_VALIDATE_PARAMS(buffer, wxT("Attempting to read from NULL buffer."));
94 if (doWrite(buffer, count) != (signed)count) {
95 throw CIOFailureException(wxT("Write error, failed to write to file."));
100 uint64 CFileDataIO::Seek(sint64 offset, wxSeekMode from) const
102 sint64 newpos = 0;
103 switch (from) {
104 case wxFromStart:
105 newpos = offset;
106 break;
108 case wxFromCurrent:
109 newpos = GetPosition() + offset;
110 break;
112 case wxFromEnd:
113 newpos = GetLength() + offset;
114 break;
116 default:
117 MULE_VALIDATE_PARAMS(false, wxT("Invalid seek-mode specified."));
120 MULE_VALIDATE_PARAMS(newpos >= 0, wxT("Position after seeking would be less than zero!"));
122 sint64 result = doSeek(newpos);
123 MULE_VALIDATE_STATE(result >= 0, wxT("Seeking resulted in invalid offset."));
124 MULE_VALIDATE_STATE(result == newpos, wxT("Target position and actual position disagree."));
126 return result;
130 uint8 CFileDataIO::ReadUInt8() const
132 uint8 value = 0;
133 Read(&value, sizeof(uint8));
135 return value;
139 uint16 CFileDataIO::ReadUInt16() const
141 uint16 value = 0;
142 Read(&value, sizeof(uint16));
144 return ENDIAN_SWAP_16(value);
148 uint32 CFileDataIO::ReadUInt32() const
150 uint32 value = 0;
151 Read(&value, sizeof(uint32));
153 return ENDIAN_SWAP_32(value);
157 uint64 CFileDataIO::ReadUInt64() const
159 uint64 value = 0;
160 Read(&value, sizeof(uint64));
162 return ENDIAN_SWAP_64(value);
166 // UInt128 values are stored a little weird way...
167 // Four little-endian 32-bit numbers, stored in
168 // big-endian order
169 CUInt128 CFileDataIO::ReadUInt128() const
171 CUInt128 value;
172 for (int i = 0; i < 4; i++) {
173 // Four 32bits chunks
174 value.Set32BitChunk(i, ReadUInt32());
177 return value;
181 CMD4Hash CFileDataIO::ReadHash() const
183 CMD4Hash value;
184 Read(value.GetHash(), MD4HASH_LENGTH);
186 return value;
190 float CFileDataIO::ReadFloat() const
192 float retVal;
193 Read(&retVal, sizeof(float));
194 return retVal;
198 unsigned char* CFileDataIO::ReadBsob(uint8* puSize) const
200 MULE_VALIDATE_PARAMS(puSize, wxT("NULL pointer argument in ReadBsob"));
202 *puSize = ReadUInt8();
204 CScopedArray<unsigned char> bsob(*puSize);
205 Read(bsob.get(), *puSize);
207 return bsob.release();
211 wxString CFileDataIO::ReadString(bool bOptUTF8, uint8 SizeLen, bool SafeRead) const
213 uint32 readLen;
214 switch (SizeLen) {
215 case sizeof(uint16): readLen = ReadUInt16(); break;
216 case sizeof(uint32): readLen = ReadUInt32(); break;
218 default:
219 MULE_VALIDATE_PARAMS(false, wxT("Invalid SizeLen value in ReadString"));
222 if (SafeRead) {
223 readLen = std::min<uint64>(readLen, GetLength() - GetPosition());
226 return ReadOnlyString(bOptUTF8, readLen);
230 wxString CFileDataIO::ReadOnlyString(bool bOptUTF8, uint16 raw_len) const
232 // We only need to set the the NULL terminator, since we know that
233 // reads will either succeed or throw an exception, in which case
234 // we wont be returning anything
235 std::vector<char> val_array(raw_len + 1);
236 val_array[raw_len] = 0;
238 char* val = &(val_array[0]);
240 Read(val, raw_len);
241 wxString str;
243 if (CHECK_BOM(raw_len, val)) {
244 // This is a UTF8 string with a BOM header, skip header.
245 str = UTF82unicode(val + 3);
246 } else if (bOptUTF8) {
247 str = UTF82unicode(val);
248 if (str.IsEmpty()) {
249 // Fallback to Latin-1
250 str = wxString(val, wxConvISO8859_1, raw_len);
252 } else {
253 // Raw strings are written as Latin-1 (see CFileDataIO::WriteStringCore)
254 str = wxString(val, wxConvISO8859_1, raw_len);
257 return str;
261 void CFileDataIO::WriteUInt8(uint8 value)
263 Write(&value, sizeof(uint8));
267 void CFileDataIO::WriteUInt16(uint16 value)
269 ENDIAN_SWAP_I_16(value);
271 Write(&value, sizeof(uint16));
275 void CFileDataIO::WriteUInt32(uint32 value)
277 ENDIAN_SWAP_I_32(value);
279 Write(&value, sizeof(uint32));
283 void CFileDataIO::WriteUInt64(uint64 value)
285 ENDIAN_SWAP_I_64(value);
287 Write(&value, sizeof(uint64));
291 // UInt128 values are stored a little weird way...
292 // Four little-endian 32-bit numbers, stored in
293 // big-endian order
294 void CFileDataIO::WriteUInt128(const Kademlia::CUInt128& value)
296 for (int i = 0; i < 4; i++) {
297 // Four 32bits chunks
298 WriteUInt32(value.Get32BitChunk(i));
303 void CFileDataIO::WriteHash(const CMD4Hash& value)
305 Write(value.GetHash(), MD4HASH_LENGTH);
309 void CFileDataIO::WriteFloat(float value)
311 Write(&value, sizeof(float));
315 void CFileDataIO::WriteBsob(const unsigned char* value, uint8 size)
317 WriteUInt8(size);
318 Write(value, size);
322 void CFileDataIO::WriteString(const wxString& str, EUtf8Str eEncode, uint8 SizeLen)
324 switch (eEncode) {
325 case utf8strRaw:
326 case utf8strOptBOM: {
327 Unicode2CharBuf s(unicode2UTF8(str));
328 if (s) {
329 WriteStringCore(s, eEncode, SizeLen);
330 break;
333 default: {
334 // Non UTF-8 strings are saved as Latin-1
335 wxCharBuffer s1 = wxConvISO8859_1.cWC2MB(str);
336 WriteStringCore(s1, utf8strNone, SizeLen);
342 void CFileDataIO::WriteStringCore(const char *s, EUtf8Str eEncode, uint8 SizeLen)
344 uint32 sLength = s ? strlen(s) : 0;
345 uint32 real_length = 0;
346 if (eEncode == utf8strOptBOM) {
347 real_length = sLength + 3; // For BOM header.
348 } else {
349 real_length = sLength;
352 switch (SizeLen) {
353 case 0:
354 // Don't write size.
355 break;
357 case sizeof(uint16):
358 // We must not allow too long strings to be written,
359 // as this would allow for a buggy clients to "poison"
360 // us, by sending ISO8859-1 strings that expand to a
361 // greater than 16b length when converted as UTF-8.
362 if (real_length > 0xFFFF) {
363 wxFAIL_MSG(wxT("String is too long to be saved"));
365 real_length = std::min<uint32>(real_length, 0xFFFF);
366 if (eEncode == utf8strOptBOM) {
367 sLength = real_length - 3;
368 } else {
369 sLength = real_length;
373 WriteUInt16(real_length);
374 break;
376 case sizeof(uint32):
377 WriteUInt32(real_length);
378 break;
380 default:
381 MULE_VALIDATE_PARAMS(false, wxT("Invalid length for string-length field."));
384 // The BOM header must be written even if the string is empty.
385 if (eEncode == utf8strOptBOM) {
386 Write(BOMHeader, 3);
389 // Only attempt to write non-NULL strings.
390 if (sLength) {
391 // No NULL terminator is written since we explicitly specify the length
392 Write(s, sLength);
397 CTag *CFileDataIO::ReadTag(bool bOptACP) const
399 CTag *retVal = NULL;
400 wxString name;
401 byte type = 0;
402 try {
403 type = ReadUInt8();
404 name = ReadString(false);
406 switch (type)
408 // NOTE: This tag data type is accepted and stored only to give us the possibility to upgrade
409 // the net in some months.
411 // And still.. it doesnt't work this way without breaking backward compatibility. To properly
412 // do this without messing up the network the following would have to be done:
413 // - those tag types have to be ignored by any client, otherwise those tags would also be sent (and
414 // that's really the problem)
416 // - ignoring means, each client has to read and right throw away those tags, so those tags get
417 // get never stored in any tag list which might be sent by that client to some other client.
419 // - all calling functions have to be changed to deal with the 'nr. of tags' attribute (which was
420 // already parsed) correctly.. just ignoring those tags here is not enough, any taglists have to
421 // be built with the knowledge that the 'nr. of tags' attribute may get decreased during the tag
422 // reading..
424 // If those new tags would just be stored and sent to remote clients, any malicious or just bugged
425 // client could let send a lot of nodes "corrupted" packets...
427 case TAGTYPE_HASH16:
429 retVal = new CTagHash(name, ReadHash());
430 break;
433 case TAGTYPE_STRING:
434 retVal = new CTagString(name, ReadString(bOptACP));
435 break;
437 case TAGTYPE_UINT64:
438 retVal = new CTagInt64(name, ReadUInt64());
439 break;
441 case TAGTYPE_UINT32:
442 retVal = new CTagInt32(name, ReadUInt32());
443 break;
445 case TAGTYPE_UINT16:
446 retVal = new CTagInt16(name, ReadUInt16());
447 break;
449 case TAGTYPE_UINT8:
450 retVal = new CTagInt8(name, ReadUInt8());
451 break;
453 case TAGTYPE_FLOAT32:
454 retVal = new CTagFloat(name, ReadFloat());
455 break;
457 // NOTE: This tag data type is accepted and stored only to give us the possibility to upgrade
458 // the net in some months.
460 // And still.. it doesnt't work this way without breaking backward compatibility
461 case TAGTYPE_BSOB:
463 uint8 size = 0;
464 CScopedArray<unsigned char> value(ReadBsob(&size));
466 retVal = new CTagBsob(name, value.get(), size);
467 break;
470 default:
471 throw wxString(CFormat(wxT("Invalid Kad tag type; type=0x%02x name=%s\n")) % type % name);
473 } catch(const CMuleException& e) {
474 AddLogLineN(e.what());
475 delete retVal;
476 throw;
477 } catch(const wxString& e) {
478 AddLogLineN(e);
479 throw;
482 return retVal;
486 void CFileDataIO::ReadTagPtrList(TagPtrList* taglist, bool bOptACP) const
488 MULE_VALIDATE_PARAMS(taglist, wxT("NULL pointer argument in ReadTagPtrList"));
490 uint32 count = ReadUInt8();
491 for (uint32 i = 0; i < count; i++)
493 CTag* tag = ReadTag(bOptACP);
494 taglist->push_back(tag);
499 void CFileDataIO::WriteTag(const CTag& tag)
503 WriteUInt8(tag.GetType());
505 if (!tag.GetName().IsEmpty()) {
506 WriteString(tag.GetName(),utf8strNone);
507 } else {
508 WriteUInt16(1);
509 WriteUInt8(tag.GetNameID());
512 switch (tag.GetType())
514 case TAGTYPE_HASH16:
515 // Do NOT use this to transfer any tags for at least half a year!!
516 WriteHash(CMD4Hash(tag.GetHash()));
517 break;
518 case TAGTYPE_STRING:
519 WriteString(tag.GetStr(), utf8strRaw); // Always UTF8
520 break;
521 case TAGTYPE_UINT64:
522 WriteUInt64(tag.GetInt());
523 break;
524 case TAGTYPE_UINT32:
525 WriteUInt32(tag.GetInt());
526 break;
527 case TAGTYPE_FLOAT32:
528 WriteFloat(tag.GetFloat());
529 break;
530 case TAGTYPE_BSOB:
531 WriteBsob(tag.GetBsob(), tag.GetBsobSize());
532 break;
533 case TAGTYPE_UINT16:
534 WriteUInt16(tag.GetInt());
535 break;
536 case TAGTYPE_UINT8:
537 WriteUInt8(tag.GetInt());
538 break;
539 case TAGTYPE_BLOB:
540 // NOTE: This will break backward compatibility with met files for eMule versions prior to 0.44a
541 // and any aMule prior to SVN 26/02/2005
542 WriteUInt32(tag.GetBlobSize());
543 Write(tag.GetBlob(), tag.GetBlobSize());
544 break;
545 default:
546 //TODO: Support more tag types
547 // With the if above, this should NEVER happen.
548 AddLogLineNS(CFormat(wxT("CFileDataIO::WriteTag: Unknown tag: type=0x%02X")) % tag.GetType());
549 wxFAIL;
550 break;
552 } catch (...) {
553 AddLogLineNS(wxT("Exception in CDataIO:WriteTag"));
554 throw;
559 void CFileDataIO::WriteTagPtrList(const TagPtrList& tagList)
561 uint32 count = tagList.size();
562 wxASSERT( count <= 0xFF );
564 WriteUInt8(count);
565 TagPtrList::const_iterator it;
566 for (it = tagList.begin(); it != tagList.end(); it++) {
567 WriteTag(**it);
571 uint64 CFileDataIO::GetIntTagValue() const {
573 uint8 type = ReadUInt8();
575 ReadString(false);
577 switch (type) {
579 case TAGTYPE_UINT64:
580 return ReadUInt64();
581 break;
583 case TAGTYPE_UINT32:
584 return ReadUInt32();
585 break;
587 case TAGTYPE_UINT16:
588 return ReadUInt16();
589 break;
591 case TAGTYPE_UINT8:
592 return ReadUInt8();
593 break;
595 default:
596 throw wxString(wxT("Wrong tag type reading int tag"));
599 // File_checked_for_headers