libdigidocpp
File.cpp
Go to the documentation of this file.
1 /*
2  * libdigidocpp
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 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  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  *
18  */
19 
20 // This file has platform-specific implementations.
21 // Treat all POSIX systems (Linux, MAC) the same way. Treat non-POSIX as Windows.
22 
23 #include "File.h"
24 
25 #include "../log.h"
26 
27 #include <algorithm>
28 #include <iostream>
29 #include <fstream>
30 #include <sstream>
31 #include <time.h>
32 #include <sys/stat.h>
33 #include <sys/types.h>
34 
35 #ifdef _WIN32
36  #include <windows.h>
37  #include <direct.h>
38 #else
39  #include <unistd.h>
40  #include <dirent.h>
41  #include <sys/param.h>
42 #endif
43 #if defined(__APPLE__)
44 #include <CoreFoundation/CFString.h>
45 #endif
46 
47 #ifdef _WIN32
48 #define PATH_DELIMITER '\\'
49 #define f_fopen _wfopen
50 #define f_remove _wremove
51 #define f_rename _wrename
52 #define f_stat _wstat
53 #define f_statbuf struct _stat
54 #else
55 #define PATH_DELIMITER '/'
56 #define f_fopen fopen
57 #define f_remove remove
58 #define f_rename rename
59 #define f_stat stat
60 #define f_statbuf struct stat
61 #endif
62 
63 #if !defined(_WIN32) && !defined(__APPLE__)
64 #include <errno.h>
65 #include <iconv.h>
66 #include <stdlib.h>
67 #include <string.h>
68 
79 std::string digidoc::util::File::convertUTF8(const std::string &str_in, bool to_UTF)
80 {
81  std::string charset("C");
82  char *env_lang = getenv("LANG");
83  if(env_lang && charset.compare(env_lang) != 0)
84  {
85  charset = env_lang;
86  size_t locale_start = charset.rfind(".");
87  if(locale_start != std::string::npos)
88  charset = charset.substr(locale_start+1);
89  }
90 
91  // no conversion needed for UTF-8
92  if((charset.compare("UTF-8") == 0) || (charset.compare("utf-8") == 0))
93  return str_in;
94 
95  iconv_t ic_descr = iconv_t(-1);
96  try
97  {
98  ic_descr = to_UTF ? iconv_open("UTF-8", charset.c_str()) : iconv_open(charset.c_str(), "UTF-8");
99  }
100  catch(std::exception &) {}
101 
102  if(ic_descr == iconv_t(-1))
103  return str_in;
104 
105  char* inptr = (char*)str_in.c_str();
106  size_t inleft = str_in.size();
107 
108  std::string out;
109  char outbuf[64];
110  char* outptr;
111  size_t outleft;
112 
113  while(inleft > 0)
114  {
115  outbuf[0] = '\0';
116  outptr = (char *)outbuf;
117  outleft = sizeof(outbuf) - sizeof(outbuf[0]);
118 
119  size_t result = iconv(ic_descr, &inptr, &inleft, &outptr, &outleft);
120  if(result == size_t(-1))
121  {
122  switch(errno)
123  {
124  case E2BIG: break;
125  case EILSEQ:
126  case EINVAL:
127  default:
128  iconv_close(ic_descr);
129  return str_in;
130  break;
131  }
132  }
133  *outptr = '\0';
134  out += outbuf;
135  }
136  iconv_close(ic_descr);
137 
138  return out;
139 }
140 #endif
141 
142 std::stack<std::string> digidoc::util::File::tempFiles;
143 
144 
146 {
147 #ifdef _WIN32
148  wchar_t *path = _wgetcwd( 0, 0 );
149 #else
150  char *path = getcwd( 0, 0 );
151 #endif
152  std::string ret;
153  if( path )
154  ret = decodeName( path );
155  free( path );
156  return ret;
157 }
158 
159 std::string digidoc::util::File::env(const std::string &varname)
160 {
161  f_string _varname = encodeName( varname );
162 #ifdef _WIN32
163  if( wchar_t *var = _wgetenv( _varname.c_str() ) )
164 #else
165  if( char *var = getenv( _varname.c_str() ) )
166 #endif
167  return decodeName( var );
168  return std::string();
169 }
170 
176 #if defined(_WIN32)
177 std::wstring digidoc::util::File::encodeName(const std::string &fileName)
178 {
179  if(fileName.empty())
180  return std::wstring();
181  int len = MultiByteToWideChar(CP_UTF8, 0, fileName.data(), int(fileName.size()), 0, 0);
182  std::wstring out(len, 0);
183  len = MultiByteToWideChar(CP_UTF8, 0, fileName.data(), int(fileName.size()), &out[0], len);
184  return out;
185 }
186 #elif defined(__APPLE__)
187 std::string digidoc::util::File::encodeName(const std::string &fileName)
188 {
189  if(fileName.empty())
190  return std::string();
191  CFMutableStringRef ref = CFStringCreateMutable(0, 0);
192  CFStringAppendCString(ref, fileName.c_str(), kCFStringEncodingUTF8);
193  CFStringNormalize(ref, kCFStringNormalizationFormD);
194 
195  char *composed = new char[fileName.size() * 2];
196  CFStringGetCString(ref, composed, fileName.size() * 2, kCFStringEncodingUTF8);
197  CFRelease(ref);
198 
199  std::string out(composed);
200  delete [] composed;
201 
202  return out;
203 }
204 #else
205 std::string digidoc::util::File::encodeName(const std::string &fileName)
206 { return convertUTF8(fileName,false); }
207 #endif
208 
214 #if defined(_WIN32)
215 std::string digidoc::util::File::decodeName(const std::wstring &localFileName)
216 {
217  if(localFileName.empty())
218  return std::string();
219  int len = WideCharToMultiByte(CP_UTF8, 0, localFileName.data(), int(localFileName.size()), 0, 0, 0, 0);
220  std::string out(len, 0);
221  len = WideCharToMultiByte(CP_UTF8, 0, localFileName.data(), int(localFileName.size()), &out[0], len, 0, 0);
222  return out;
223 }
224 #elif defined(__APPLE__)
225 std::string digidoc::util::File::decodeName(const std::string &localFileName)
226 {
227  if(localFileName.empty())
228  return std::string();
229  CFMutableStringRef ref = CFStringCreateMutable(0, 0);
230  CFStringAppendCString(ref, localFileName.c_str(), kCFStringEncodingUTF8);
231  CFStringNormalize(ref, kCFStringNormalizationFormC);
232 
233  char *composed = new char[localFileName.size() * 2];
234  CFStringGetCString(ref, composed, localFileName.size() * 2, kCFStringEncodingUTF8);
235  CFRelease(ref);
236 
237  std::string out(composed);
238  delete [] composed;
239 
240  return out;
241 }
242 #else
243 std::string digidoc::util::File::decodeName(const std::string &localFileName)
244 { return convertUTF8(localFileName,true); }
245 #endif
246 
253 bool digidoc::util::File::fileExists(const std::string& path)
254 {
255  f_statbuf fileInfo;
256  f_string _path = encodeName(path);
257  if(f_stat(_path.c_str(), &fileInfo) != 0)
258  return false;
259 
260  // XXX: != S_IFREG
261  if((fileInfo.st_mode & S_IFMT) == S_IFDIR)
262  return false;
263 
264  return true;
265 }
266 
273 bool digidoc::util::File::directoryExists(const std::string& path)
274 {
275  f_string _path = encodeName(path);
276 #ifdef _WIN32
277  // stat will fail on win32 if path ends with backslash
278  if(!_path.empty() && (_path[_path.size() - 1] == L'/' || _path[_path.size() - 1] == L'\\'))
279  _path = _path.substr(0, _path.size() - 1);
280  // TODO:XXX: "C:" is not a directory, so create recursively will
281  // do stack overflow in case first-dir in root doesn't exist.
282 #endif
283 
284  f_statbuf fileInfo;
285  if(f_stat(_path.c_str(), &fileInfo) != 0)
286  return false;
287 
288  if((fileInfo.st_mode & S_IFMT) != S_IFDIR)
289  return false;
290 
291  return true;
292 }
293 
300 tm* digidoc::util::File::modifiedTime(const std::string &path)
301 {
302  f_string _path = encodeName(path);
303  f_statbuf fileInfo;
304  f_stat(_path.c_str(), &fileInfo);
305  return gmtime(&fileInfo.st_mtime);
306 }
307 
314 std::string digidoc::util::File::fileName(const std::string& path)
315 {
316  size_t pos = path.find_last_of("/\\");
317  return pos == std::string::npos ? path : path.substr(pos + 1);
318 }
319 
326 std::string digidoc::util::File::directory(const std::string& path)
327 {
328  //size_t pos = path.find_last_of("/\\");
329  size_t pos = path.find_last_of(PATH_DELIMITER);
330  return pos == std::string::npos ? "" : path.substr(0, pos);
331 }
332 
343 std::string digidoc::util::File::path(const std::string& directory, const std::string& relativePath, bool unixStyle)
344 {
345  std::string dir(directory);
346  if(!dir.empty() && (dir[dir.size() - 1] == '/' || dir[dir.size() - 1] == '\\'))
347  {
348  dir = dir.substr(0, dir.size() - 1);
349  }
350 
351  std::string path = dir + PATH_DELIMITER + relativePath;
352  if(unixStyle)
353  {
354  std::replace(path.begin(), path.end(), '\\', '/');
355  }
356  else
357  {
358 #ifdef _POSIX_VERSION
359  std::replace(path.begin(), path.end(), '\\', '/');
360 #else
361  std::replace(path.begin(), path.end(), '/', '\\');
362 #endif
363  }
364 
365  return path;
366 }
367 
372 {
373 #ifdef _WIN32
374  // requires TMP environment variable to be set
375  wchar_t *fileName = _wtempnam(0, 0); // TODO: static buffer, not thread-safe
376  if ( !fileName )
377  THROW_IOEXCEPTION("Failed to create a temporary file name.");
378 #else
379  char *fileName = tempnam(0, 0);
380  if ( !fileName )
381  THROW_IOEXCEPTION("Failed to create a temporary file name.");
382 #endif
383  std::string path = decodeName(fileName);
384  free(fileName);
385  tempFiles.push(path);
386  return path;
387 }
388 
393 {
394  return tempFileName() + PATH_DELIMITER;
395 }
396 
404 void digidoc::util::File::createDirectory(const std::string& path) throw(IOException)
405 {
406  if(path.empty())
407  {
408  THROW_IOEXCEPTION("Can not create directory with no name.");
409  }
410 
411  if(directoryExists(path))
412  {
413  return;
414  }
415 
416  std::string parentDir(path);
417  if(parentDir[parentDir.size() - 1] == '/' || parentDir[parentDir.size() - 1] == '\\')
418  {
419  parentDir = parentDir.substr(0, parentDir.size() - 1);
420  }
421  parentDir = parentDir.substr(0, parentDir.find_last_of("/\\"));
422 
423  if(!directoryExists(parentDir))
424  {
425  createDirectory(parentDir);
426  }
427 
428  f_string _path = encodeName(path);
429 #ifdef _WIN32
430  int result = _wmkdir(_path.c_str());
431  if ( result )
432  DEBUG("Creating directory '%s' failed with errno = %d", _path.c_str(), errno);
433  else
434  DEBUG("Created directory '%s'", _path.c_str());
435 #else
436  umask(0);
437  int result = mkdir(_path.c_str(), 0700);
438  DEBUG("Created directory '%s' with result = %d", _path.c_str(), result);
439 #endif
440 
441  if(result || !directoryExists(path))
442  {
443  THROW_IOEXCEPTION("Failed to create directory '%s'", path.c_str());
444  }
445 }
446 
455 {
456  std::string directory = tempDirectory();
457  createDirectory(directory);
458  return directory;
459 }
460 
468 unsigned long digidoc::util::File::fileSize(const std::string& path) throw(IOException)
469 {
470  f_statbuf fileInfo;
471  f_string _path = encodeName(path);
472  if(f_stat(_path.c_str(), &fileInfo) != 0)
473  THROW_IOEXCEPTION("File '%s' does not exist.", _path.c_str());
474 
475  if((fileInfo.st_mode & S_IFMT) == S_IFDIR)
476  THROW_IOEXCEPTION("'%s' is not a file.", _path.c_str());
477 
478  if (fileInfo.st_size < 0)
479  THROW_IOEXCEPTION("Failed to get size for file '%s'.", _path.c_str());
480 
481  return static_cast<unsigned long>(fileInfo.st_size);
482 }
483 
489 bool digidoc::util::File::isRelative(const std::string &path)
490 {
491  f_string _path = encodeName(path);
492  if( _path.length() == 0 ) return true;
493  if( _path[0] == '/' ) return false;
494 #ifdef _WIN32
495  // drive, e.g. "a:", or UNC root, e.q. "//"
496  if( _path.length() >= 2 &&
497  ((iswalpha(_path[0]) && _path[1] == ':') ||
498  (_path[0] == '/' && _path[1] == '/')) )
499  return false;
500 #endif
501  return true;
502 }
503 
519 std::vector<std::string> digidoc::util::File::listFiles(const std::string& directory, bool relative,
520  bool listEmptyDirectories, bool unixStyle) throw(IOException)
521 {
522  // Why, oh why does this function use vectors, not std::lists? Does anybody need to do heavy random access on the file names?
523  std::string pathDelim; pathDelim += PATH_DELIMITER;
524  if(unixStyle)
525  {
526  pathDelim = "/";
527  }
528  std::vector<std::string> files;
529 
530  // List entries in sub folders.
531  std::vector<std::string> subDirectories = getDirSubElements(directory, true, false, unixStyle);
532  std::vector<std::string>::const_iterator subDirectory = subDirectories.begin();
533  for ( ; subDirectory != subDirectories.end(); subDirectory++ )
534  {
535  // Get files in sub directory.
536  std::vector<std::string> list = listFiles(path(directory, *subDirectory), relative, listEmptyDirectories, unixStyle);
537 
538  if(!list.empty())
539  {
540  for(std::vector<std::string>::const_iterator iter = list.begin(); iter != list.end(); iter++)
541  {
542  if(relative)
543  files.push_back(path(*subDirectory, *iter, unixStyle));
544  else
545  files.push_back(*iter);
546  }
547  }
548  else
549  {
550  if(listEmptyDirectories)
551  {
552  if(relative)
553  files.push_back(std::string(*subDirectory) + pathDelim);
554  else
555  files.push_back(path(directory, *subDirectory, unixStyle) + pathDelim);
556  }
557  }
558  }
559 
560  // List files directly in current directory
561  std::vector<std::string> dirFiles = getDirSubElements(directory, relative, true, unixStyle);
562  files.insert(files.begin(), dirFiles.begin(), dirFiles.end());
563 
564  return files;
565 }
566 
576 void digidoc::util::File::copyFile(const std::string& srcPath, const std::string& destPath, bool overwrite) throw(IOException)
577 {
578  if(!fileExists(srcPath))
579  {
580  THROW_IOEXCEPTION("Source file '%s' does not exist.", srcPath.c_str());
581  }
582 
583  if(!overwrite && fileExists(destPath))
584  {
585  THROW_IOEXCEPTION("Destination file exists '%s' can not copy to there. Overwrite flag is set to false.", destPath.c_str());
586  }
587 
588  // Copy file.
589  std::ifstream ifs(encodeName(srcPath).c_str(), std::ios::binary);
590  std::ofstream ofs(encodeName(destPath).c_str(), std::ios::binary | std::ios::trunc);
591 
592  ofs << ifs.rdbuf();
593 
594  ifs.close();
595  ofs.close();
596 
597  if(ifs.fail() || ofs.fail())
598  {
599  THROW_IOEXCEPTION("Failed to copy file '%s' to '%s'.", srcPath.c_str(), destPath.c_str());
600  }
601 }
602 
612 void digidoc::util::File::moveFile(const std::string& srcPath, const std::string& destPath, bool overwrite) throw(IOException)
613 {
614  if(!fileExists(srcPath))
615  {
616  THROW_IOEXCEPTION("Source file '%s' does not exist.", srcPath.c_str());
617  }
618 
619  if(!overwrite && fileExists(destPath))
620  {
621  THROW_IOEXCEPTION("Destination file exists '%s' can not move to there. Overwrite flag is set to false.", destPath.c_str());
622  }
623 
624  f_string _srcPath = encodeName(srcPath);
625  f_string _destPath = encodeName(destPath);
626  int result = f_rename(_srcPath.c_str(), _destPath.c_str());
627  if ( result != 0 )
628  {
629  // -=K=-: copy and remove source should work as move between different partitions
630  copyFile( srcPath, destPath, overwrite );
631  result = f_remove( _srcPath.c_str() );
632  }
633  if ( result != 0 )
634  {
635  // -=K=-: suceeded to copy, failed to remove. Should we throw or warn?
636  WARN( "Failed to remove source file '%s' when moving it to '%s'.", _srcPath.c_str(), _destPath.c_str() );
637  }
638 
639 }
640 
652 std::vector<std::string> digidoc::util::File::getDirSubElements(const std::string& directory,
653  bool relative, bool filesOnly, bool unixStyle) throw(IOException)
654 {
655  std::vector<std::string> files;
656 
657 #ifdef _POSIX_VERSION
658 
659  std::string _directory = encodeName(directory);
660  DIR* pDir = opendir(_directory.c_str());
661  if(!pDir)
662  {
663  THROW_IOEXCEPTION("Failed to open directory '%s'", _directory.c_str());
664  }
665 
666  char fullPath[MAXPATHLEN];
667  struct stat info;
668  dirent* entry;
669  while((entry = readdir(pDir)) != NULL)
670  {
671  if(std::string(".").compare(entry->d_name) == 0
672  || std::string("..").compare(entry->d_name) == 0)
673  {
674  continue;
675  }
676 
677  sprintf(fullPath, "%s/%s", _directory.c_str(), entry->d_name);
678  lstat(fullPath, &info);
679 
680  if((!filesOnly && (entry->d_type == 0x04||S_ISDIR(info.st_mode))) // Directory
681  || (filesOnly && (entry->d_type == 0x08||S_ISREG(info.st_mode)))) // File
682  {
683  if(relative)
684  files.push_back(decodeName(entry->d_name));
685  else
686  files.push_back(path(directory, decodeName(entry->d_name), unixStyle));
687  }
688  }
689 
690  closedir(pDir);
691 
692 #else
693 
694  WIN32_FIND_DATAW findFileData;
695  HANDLE hFind = NULL;
696 
697  try
698  {
699  if ( directory.size() > MAX_PATH )
700  {
701  // MSDN: "Relative paths are limited to MAX_PATH characters." - can this be true?
702  THROW_IOEXCEPTION("Directory path '%s' exceeds the limit %d", directory.c_str(), MAX_PATH);
703  }
704 
705  std::wstring findPattern = encodeName(directory + "\\*");
706  hFind = ::FindFirstFileW(findPattern.c_str(), &findFileData);
707  if (hFind == INVALID_HANDLE_VALUE)
708  {
709  THROW_IOEXCEPTION("Listing contents of directory '%s' failed with error %d", directory.c_str(), ::GetLastError());
710  }
711 
712  do
713  {
714  std::wstring fileName(findFileData.cFileName);
715  if ( fileName == L"." || fileName == L".." )
716  {
717  continue; // skip those too
718  }
719 
720  if(!filesOnly && (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) // Directory
721  || filesOnly && !(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) // File
722  {
723  if(relative)
724  files.push_back(decodeName(fileName));
725  else
726  files.push_back(path(directory, decodeName(fileName), unixStyle));
727  }
728  } while ( ::FindNextFileW(hFind, &findFileData) != FALSE );
729 
730  // double-check for errors
731  if ( ::GetLastError() != ERROR_NO_MORE_FILES )
732  {
733  THROW_IOEXCEPTION("Listing contents of directory '%s' failed with error %d", directory.c_str(), ::GetLastError());
734  }
735 
736  ::FindClose(hFind);
737  }
738  catch (...)
739  {
740  ::FindClose(hFind);
741  throw;
742  }
743 
744 
745 #endif
746 
747  return files;
748 }
749 
750 FILE* digidoc::util::File::fopen(const std::string &filename, const std::string &mode)
751 {
752  return ::f_fopen(encodeName(filename).c_str(), encodeName(mode).c_str());
753 }
754 
763 std::string digidoc::util::File::fullPathUrl(const std::string& fullDirectory, const std::string& relativeFilePath)
764 {
765  std::string result(fullDirectory);
766  // Under windows replace the path delimiters
767 #ifndef _POSIX_VERSION
768  std::replace(result.begin(), result.end(), '\\', '/');
769  return digidoc::util::File::toUri("file:///" + result + "/" + relativeFilePath);
770 #endif
771  return digidoc::util::File::toUri("file://" + result + "/" + relativeFilePath);
772 }
773 
780 void digidoc::util::File::removeFile(const std::string& fname)
781 {
782  f_string _fname = encodeName(fname);
783  if ( f_remove( _fname.c_str() ) )
784  WARN( "Tried to remove the temporary file or directory '%s', but failed.", _fname.c_str() );
785 }
786 
792 void digidoc::util::File::removeDirectory(const std::string& dname)
793 {
794  f_string _dname = encodeName(dname);
795 #ifdef _WIN32
796  if (!RemoveDirectoryW(_dname.c_str()))
797 #else
798  if (remove( _dname.c_str() ) != 0)
799 #endif
800  WARN( "Tried to remove the temporary directory '%s', but failed.", _dname.c_str() );
801 }
802 
809 {
810  // First delete recursively all subdirectories
811  std::vector<std::string> subDirs = getDirSubElements(dname, false, false, false);
812  for (std::vector<std::string>::reverse_iterator it = subDirs.rbegin(); it != subDirs.rend(); it++)
813  {
814  removeDirectoryRecursively(*it);
815  }
816 
817  // Then delete all files inside this directory
818  std::vector<std::string> subFiles = getDirSubElements(dname, false, true, false);
819  for (std::vector<std::string>::reverse_iterator it = subFiles.rbegin(); it != subFiles.rend(); it++)
820  {
821  DEBUG( "Deleting the temporary file '%s'", it->c_str() );
822  removeFile(*it);
823  }
824 
825  // Then delete the directory itself. It should now be empty.
826  DEBUG( "Deleting the temporary directory '%s'", dname.c_str() );
827  removeDirectory(dname);
828 }
829 
835 {
836  while (!tempFiles.empty())
837  {
838  if ( directoryExists(tempFiles.top()) )
839  removeDirectoryRecursively(tempFiles.top());
840  else
841  removeFile(tempFiles.top());
842  tempFiles.pop();
843  }
844 }
845 
856 std::string digidoc::util::File::toUri(const std::string &path)
857 {
858  static std::string legal_chars = "-_.!~*'();/?:@&=+$,";
859  std::ostringstream dst;
860  for(std::string::const_iterator i = path.begin(); i != path.end(); ++i)
861  {
862  if( ((*i >= 0x61 && *i <= 0x7A) // ALPHA
863  || (*i >= 0x41 && *i <= 0x5A) // ALPHA
864  || (*i >= 0x30 && *i <= 0x39)) // DIGIT
865  || legal_chars.find(*i) != std::string::npos )
866  dst << *i;
867  else
868  dst << '%' << std::hex << std::uppercase << (static_cast<int>(*i) & 0xFF);
869  }
870  return dst.str();
871 }
872 
873 static std::string hexToUpper(const std::string &in)
874 {
875  std::ostringstream dst;
876  for(std::string::const_iterator i = in.begin(); i != in.end(); ++i)
877  {
878  if( *i == '%' )
879  {
880  dst << '%' << char(toupper( *(i + 1) )) << char(toupper( *(i + 2) ));
881  i += 2;
882  }
883  else
884  dst << *i;
885  }
886 
887  return dst.str();
888 }
889 
890 bool digidoc::util::File::compareUri(const std::string &a, const std::string &b)
891 {
892  return hexToUpper(a) == hexToUpper(b);
893 }
894 
895 std::string digidoc::util::File::fromUri(const std::string &uri)
896 {
897  std::ostringstream dst;
898  for(std::string::const_iterator i = uri.begin(); i != uri.end(); ++i)
899  {
900  if( *i == '%' )
901  {
902  const char from[2] = { *(i + 1), *(i + 2) };
903  dst << char(strtol( from, 0, 16 ));
904  i += 2;
905  }
906  else
907  dst << *i;
908  }
909  return dst.str();
910 }