00001 
00002 
00003 
00004 
00005 
00006 
00007 
00008 
00009 
00010 #include <boost/asio.hpp>
00011 #include <boost/bind.hpp>
00012 #include <boost/assert.hpp>
00013 #include <boost/lexical_cast.hpp>
00014 #include <boost/filesystem/operations.hpp>
00015 #include <boost/filesystem/fstream.hpp>
00016 #include <boost/algorithm/string/case_conv.hpp>
00017 #include <boost/exception/diagnostic_information.hpp>
00018 
00019 #include "FileService.hpp"
00020 #include <pion/error.hpp>
00021 #include <pion/plugin.hpp>
00022 #include <pion/algorithm.hpp>
00023 #include <pion/http/response_writer.hpp>
00024 
00025 using namespace pion;
00026 
00027 namespace pion {        
00028 namespace plugins {     
00029 
00030 
00031 
00032 
00033 const std::string           FileService::DEFAULT_MIME_TYPE("application/octet-stream");
00034 const unsigned int          FileService::DEFAULT_CACHE_SETTING = 1;
00035 const unsigned int          FileService::DEFAULT_SCAN_SETTING = 0;
00036 const unsigned long         FileService::DEFAULT_MAX_CACHE_SIZE = 0;    
00037 const unsigned long         FileService::DEFAULT_MAX_CHUNK_SIZE = 0;    
00038 boost::once_flag            FileService::m_mime_types_init_flag = BOOST_ONCE_INIT;
00039 FileService::MIMETypeMap    *FileService::m_mime_types_ptr = NULL;
00040 
00041 
00042 
00043 
00044 FileService::FileService(void)
00045     : m_logger(PION_GET_LOGGER("pion.FileService")),
00046     m_cache_setting(DEFAULT_CACHE_SETTING),
00047     m_scan_setting(DEFAULT_SCAN_SETTING),
00048     m_max_cache_size(DEFAULT_MAX_CACHE_SIZE),
00049     m_max_chunk_size(DEFAULT_MAX_CHUNK_SIZE),
00050     m_writable(false)
00051 {}
00052 
00053 void FileService::set_option(const std::string& name, const std::string& value)
00054 {
00055     if (name == "directory") {
00056         m_directory = value;
00057         m_directory.normalize();
00058         plugin::check_cygwin_path(m_directory, value);
00059         
00060         if (! boost::filesystem::exists(m_directory) || ! boost::filesystem::is_directory(m_directory)) {
00061 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00062             const std::string dir_name = m_directory.string();
00063 #else
00064             const std::string dir_name = m_directory.directory_string();
00065 #endif
00066             BOOST_THROW_EXCEPTION( error::directory_not_found() << error::errinfo_dir_name(dir_name) );
00067         }
00068     } else if (name == "file") {
00069         m_file = value;
00070         plugin::check_cygwin_path(m_file, value);
00071         
00072         if (! boost::filesystem::exists(m_file) || boost::filesystem::is_directory(m_file)) {
00073 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00074             const std::string file_name = m_file.string();
00075 #else
00076             const std::string file_name = m_file.file_string();
00077 #endif
00078             BOOST_THROW_EXCEPTION( error::file_not_found() << error::errinfo_file_name(file_name) );
00079         }
00080     } else if (name == "cache") {
00081         if (value == "0") {
00082             m_cache_setting = 0;
00083         } else if (value == "1") {
00084             m_cache_setting = 1;
00085         } else if (value == "2") {
00086             m_cache_setting = 2;
00087         } else {
00088             BOOST_THROW_EXCEPTION( error::bad_arg() << error::errinfo_arg_name(name) );
00089         }
00090     } else if (name == "scan") {
00091         if (value == "0") {
00092             m_scan_setting = 0;
00093         } else if (value == "1") {
00094             m_scan_setting = 1;
00095         } else if (value == "2") {
00096             m_scan_setting = 2;
00097         } else if (value == "3") {
00098             m_scan_setting = 3;
00099         } else {
00100             BOOST_THROW_EXCEPTION( error::bad_arg() << error::errinfo_arg_name(name) );
00101         }
00102     } else if (name == "max_chunk_size") {
00103         m_max_chunk_size = boost::lexical_cast<unsigned long>(value);
00104     } else if (name == "writable") {
00105         if (value == "true") {
00106             m_writable = true;
00107         } else if (value == "false") {
00108             m_writable = false;
00109         } else {
00110             BOOST_THROW_EXCEPTION( error::bad_arg() << error::errinfo_arg_name(name) );
00111         }
00112     } else {
00113         BOOST_THROW_EXCEPTION( error::bad_arg() << error::errinfo_arg_name(name) );
00114     }
00115 }
00116 
00117 void FileService::operator()(http::request_ptr& http_request_ptr, tcp::connection_ptr& tcp_conn)
00118 {
00119     
00120     const std::string relative_path(get_relative_resource(http_request_ptr->get_resource()));
00121 
00122     
00123     boost::filesystem::path file_path;
00124     if (relative_path.empty()) {
00125         
00126 
00127         if (m_file.empty()) {
00128             
00129             PION_LOG_WARN(m_logger, "No file option defined ("
00130                           << get_resource() << ")");
00131             sendNotFoundResponse(http_request_ptr, tcp_conn);
00132             return;
00133         } else {
00134             file_path = m_file;
00135         }
00136     } else {
00137         
00138 
00139         if (m_directory.empty()) {
00140             
00141             PION_LOG_WARN(m_logger, "No directory option defined ("
00142                           << get_resource() << "): " << relative_path);
00143             sendNotFoundResponse(http_request_ptr, tcp_conn);
00144             return;
00145         } else {
00146             file_path = m_directory / relative_path;
00147         }
00148     }
00149 
00150     
00151     file_path.normalize();
00152 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00153     std::string file_string = file_path.string();
00154     if (file_string.find(m_directory.string()) != 0) {
00155 #else
00156     std::string file_string = file_path.file_string();
00157     if (file_string.find(m_directory.directory_string()) != 0) {
00158 #endif
00159         PION_LOG_WARN(m_logger, "Request for file outside of directory ("
00160                       << get_resource() << "): " << relative_path);
00161         static const std::string FORBIDDEN_HTML_START =
00162             "<html><head>\n"
00163             "<title>403 Forbidden</title>\n"
00164             "</head><body>\n"
00165             "<h1>Forbidden</h1>\n"
00166             "<p>The requested URL ";
00167         static const std::string FORBIDDEN_HTML_FINISH =
00168             " is not in the configured directory.</p>\n"
00169             "</body></html>\n";
00170         http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00171                                      boost::bind(&tcp::connection::finish, tcp_conn)));
00172         writer->get_response().set_status_code(http::types::RESPONSE_CODE_FORBIDDEN);
00173         writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_FORBIDDEN);
00174         if (http_request_ptr->get_method() != http::types::REQUEST_METHOD_HEAD) {
00175             writer->write_no_copy(FORBIDDEN_HTML_START);
00176             writer << algorithm::xml_encode(http_request_ptr->get_resource());
00177             writer->write_no_copy(FORBIDDEN_HTML_FINISH);
00178         }
00179         writer->send();
00180         return;
00181     }
00182 
00183     
00184     if (boost::filesystem::is_directory(file_path)) {
00185         PION_LOG_WARN(m_logger, "Request for directory ("
00186                       << get_resource() << "): " << relative_path);
00187         static const std::string FORBIDDEN_HTML_START =
00188             "<html><head>\n"
00189             "<title>403 Forbidden</title>\n"
00190             "</head><body>\n"
00191             "<h1>Forbidden</h1>\n"
00192             "<p>The requested URL ";
00193         static const std::string FORBIDDEN_HTML_FINISH =
00194             " is a directory.</p>\n"
00195             "</body></html>\n";
00196         http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00197                                      boost::bind(&tcp::connection::finish, tcp_conn)));
00198         writer->get_response().set_status_code(http::types::RESPONSE_CODE_FORBIDDEN);
00199         writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_FORBIDDEN);
00200         if (http_request_ptr->get_method() != http::types::REQUEST_METHOD_HEAD) {
00201             writer->write_no_copy(FORBIDDEN_HTML_START);
00202             writer << algorithm::xml_encode(http_request_ptr->get_resource());
00203             writer->write_no_copy(FORBIDDEN_HTML_FINISH);
00204         }
00205         writer->send();
00206         return;
00207     }
00208 
00209     if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_GET 
00210         || http_request_ptr->get_method() == http::types::REQUEST_METHOD_HEAD)
00211     {
00212         
00213         enum ResponseType {
00214             RESPONSE_UNDEFINED,     
00215             RESPONSE_OK,            
00216             RESPONSE_HEAD_OK,       
00217             RESPONSE_NOT_FOUND,     
00218             RESPONSE_NOT_MODIFIED   
00219         } response_type = RESPONSE_UNDEFINED;
00220 
00221         
00222         DiskFile response_file;
00223 
00224         
00225         const std::string if_modified_since(http_request_ptr->get_header(http::types::HEADER_IF_MODIFIED_SINCE));
00226 
00227         
00228         
00229         if (m_cache_setting > 0 || m_scan_setting > 0) {
00230 
00231             
00232             boost::mutex::scoped_lock cache_lock(m_cache_mutex);
00233             CacheMap::iterator cache_itr = m_cache_map.find(relative_path);
00234 
00235             if (cache_itr == m_cache_map.end()) {
00236                 
00237 
00238                 if (m_scan_setting == 1 || m_scan_setting == 3) {
00239                     
00240                     
00241                     
00242                     PION_LOG_WARN(m_logger, "Request for unknown file ("
00243                                   << get_resource() << "): " << relative_path);
00244                     response_type = RESPONSE_NOT_FOUND;
00245                 } else {
00246                     PION_LOG_DEBUG(m_logger, "No cache entry for request ("
00247                                    << get_resource() << "): " << relative_path);
00248                 }
00249 
00250             } else {
00251                 
00252 
00253                 PION_LOG_DEBUG(m_logger, "Found cache entry for request ("
00254                                << get_resource() << "): " << relative_path);
00255 
00256                 if (m_cache_setting == 0) {
00257                     
00258 
00259                     
00260                     response_file.setFilePath(cache_itr->second.getFilePath());
00261                     response_file.setMimeType(cache_itr->second.getMimeType());
00262 
00263                     
00264                     response_file.update();
00265 
00266                     
00267                     if (response_file.getLastModifiedString() == if_modified_since) {
00268                         
00269                         response_type = RESPONSE_NOT_MODIFIED;
00270                     } else {
00271                         if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_HEAD) {
00272                             response_type = RESPONSE_HEAD_OK;
00273                         } else {
00274                             response_type = RESPONSE_OK;
00275                             PION_LOG_DEBUG(m_logger, "Cache disabled, reading file ("
00276                                            << get_resource() << "): " << relative_path);
00277                         }
00278                     }
00279 
00280                 } else {
00281                     
00282 
00283                     
00284                     bool cache_was_updated = false;
00285 
00286                     if (cache_itr->second.getLastModified() == 0) {
00287 
00288                         
00289                         cache_was_updated = true;
00290                         cache_itr->second.update();
00291                         if (m_max_cache_size==0 || cache_itr->second.getFileSize() <= m_max_cache_size) {
00292                             
00293                             cache_itr->second.read();
00294                         } else {
00295                             cache_itr->second.resetFileContent();
00296                         }
00297 
00298                     } else if (m_cache_setting == 1) {
00299 
00300                         
00301                         cache_was_updated = cache_itr->second.checkUpdated();
00302 
00303                     } 
00304 
00305                     
00306                     if (cache_itr->second.getLastModifiedString() == if_modified_since) {
00307                         response_type = RESPONSE_NOT_MODIFIED;
00308                     } else if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_HEAD) {
00309                         response_type = RESPONSE_HEAD_OK;
00310                     } else {
00311                         response_type = RESPONSE_OK;
00312                     }
00313 
00314                     
00315                     response_file = cache_itr->second;
00316 
00317                     PION_LOG_DEBUG(m_logger, (cache_was_updated ? "Updated" : "Using")
00318                                    << " cache entry for request ("
00319                                    << get_resource() << "): " << relative_path);
00320                 }
00321             }
00322         }
00323 
00324         if (response_type == RESPONSE_UNDEFINED) {
00325             
00326             if (! boost::filesystem::exists(file_path)) {
00327                 PION_LOG_WARN(m_logger, "File not found ("
00328                               << get_resource() << "): " << relative_path);
00329                 sendNotFoundResponse(http_request_ptr, tcp_conn);
00330                 return;
00331             }
00332 
00333             response_file.setFilePath(file_path);
00334 
00335             PION_LOG_DEBUG(m_logger, "Found file for request ("
00336                            << get_resource() << "): " << relative_path);
00337 
00338             
00339 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00340             response_file.setMimeType(findMIMEType( response_file.getFilePath().filename().string()));
00341 #else
00342             response_file.setMimeType(findMIMEType( response_file.getFilePath().leaf() ));
00343 #endif
00344 
00345             
00346             response_file.update();
00347 
00348             
00349             if (response_file.getLastModifiedString() == if_modified_since) {
00350                 
00351                 response_type = RESPONSE_NOT_MODIFIED;
00352             } else if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_HEAD) {
00353                 response_type = RESPONSE_HEAD_OK;
00354             } else {
00355                 response_type = RESPONSE_OK;
00356                 if (m_cache_setting != 0) {
00357                     if (m_max_cache_size==0 || response_file.getFileSize() <= m_max_cache_size) {
00358                         
00359                         response_file.read();
00360                     }
00361                     
00362                     PION_LOG_DEBUG(m_logger, "Adding cache entry for request ("
00363                                    << get_resource() << "): " << relative_path);
00364                     boost::mutex::scoped_lock cache_lock(m_cache_mutex);
00365                     m_cache_map.insert( std::make_pair(relative_path, response_file) );
00366                 }
00367             }
00368         }
00369 
00370         if (response_type == RESPONSE_OK) {
00371             
00372             DiskFileSenderPtr sender_ptr(DiskFileSender::create(response_file,
00373                                                                 http_request_ptr, tcp_conn,
00374                                                                 m_max_chunk_size));
00375             sender_ptr->send();
00376         } else if (response_type == RESPONSE_NOT_FOUND) {
00377             sendNotFoundResponse(http_request_ptr, tcp_conn);
00378         } else {
00379             
00380 
00381             
00382             http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00383                                          boost::bind(&tcp::connection::finish, tcp_conn)));
00384             writer->get_response().set_content_type(response_file.getMimeType());
00385 
00386             
00387             writer->get_response().add_header(http::types::HEADER_LAST_MODIFIED,
00388                                             response_file.getLastModifiedString());
00389 
00390             switch(response_type) {
00391                 case RESPONSE_UNDEFINED:
00392                 case RESPONSE_NOT_FOUND:
00393                 case RESPONSE_OK:
00394                     
00395                     BOOST_ASSERT(false);
00396                     break;
00397                 case RESPONSE_NOT_MODIFIED:
00398                     
00399                     writer->get_response().set_status_code(http::types::RESPONSE_CODE_NOT_MODIFIED);
00400                     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NOT_MODIFIED);
00401                     break;
00402                 case RESPONSE_HEAD_OK:
00403                     
00404                     writer->get_response().set_status_code(http::types::RESPONSE_CODE_OK);
00405                     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_OK);
00406                     break;
00407             }
00408 
00409             
00410             writer->send();
00411         }
00412     } else if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_POST
00413                || http_request_ptr->get_method() == http::types::REQUEST_METHOD_PUT
00414                || http_request_ptr->get_method() == http::types::REQUEST_METHOD_DELETE)
00415     {
00416         
00417         if (!m_writable) {
00418             static const std::string NOT_ALLOWED_HTML_START =
00419                 "<html><head>\n"
00420                 "<title>405 Method Not Allowed</title>\n"
00421                 "</head><body>\n"
00422                 "<h1>Not Allowed</h1>\n"
00423                 "<p>The requested method ";
00424             static const std::string NOT_ALLOWED_HTML_FINISH =
00425                 " is not allowed on this server.</p>\n"
00426                 "</body></html>\n";
00427             http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00428                                          boost::bind(&tcp::connection::finish, tcp_conn)));
00429             writer->get_response().set_status_code(http::types::RESPONSE_CODE_METHOD_NOT_ALLOWED);
00430             writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_METHOD_NOT_ALLOWED);
00431             writer->write_no_copy(NOT_ALLOWED_HTML_START);
00432             writer << algorithm::xml_encode(http_request_ptr->get_method());
00433             writer->write_no_copy(NOT_ALLOWED_HTML_FINISH);
00434             writer->get_response().add_header("Allow", "GET, HEAD");
00435             writer->send();
00436         } else {
00437             http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00438                                          boost::bind(&tcp::connection::finish, tcp_conn)));
00439             if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_POST
00440                 || http_request_ptr->get_method() == http::types::REQUEST_METHOD_PUT)
00441             {
00442                 if (boost::filesystem::exists(file_path)) {
00443                     writer->get_response().set_status_code(http::types::RESPONSE_CODE_NO_CONTENT);
00444                     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NO_CONTENT);
00445                 } else {
00446                     
00447                     
00448                     if (!boost::filesystem::exists(file_path.branch_path())) {
00449                         static const std::string NOT_FOUND_HTML_START =
00450                             "<html><head>\n"
00451                             "<title>404 Not Found</title>\n"
00452                             "</head><body>\n"
00453                             "<h1>Not Found</h1>\n"
00454                             "<p>The directory of the requested URL ";
00455                         static const std::string NOT_FOUND_HTML_FINISH =
00456                             " was not found on this server.</p>\n"
00457                             "</body></html>\n";
00458                         writer->get_response().set_status_code(http::types::RESPONSE_CODE_NOT_FOUND);
00459                         writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NOT_FOUND);
00460                         writer->write_no_copy(NOT_FOUND_HTML_START);
00461                         writer << algorithm::xml_encode(http_request_ptr->get_resource());
00462                         writer->write_no_copy(NOT_FOUND_HTML_FINISH);
00463                         writer->send();
00464                         return;
00465                     }
00466                     static const std::string CREATED_HTML_START =
00467                         "<html><head>\n"
00468                         "<title>201 Created</title>\n"
00469                         "</head><body>\n"
00470                         "<h1>Created</h1>\n"
00471                         "<p>";
00472                     static const std::string CREATED_HTML_FINISH =
00473                         "</p>\n"
00474                         "</body></html>\n";
00475                     writer->get_response().set_status_code(http::types::RESPONSE_CODE_CREATED);
00476                     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_CREATED);
00477                     writer->get_response().add_header(http::types::HEADER_LOCATION, http_request_ptr->get_resource());
00478                     writer->write_no_copy(CREATED_HTML_START);
00479                     writer << algorithm::xml_encode(http_request_ptr->get_resource());
00480                     writer->write_no_copy(CREATED_HTML_FINISH);
00481                 }
00482                 std::ios_base::openmode mode = http_request_ptr->get_method() == http::types::REQUEST_METHOD_POST?
00483                                                std::ios::app : std::ios::out;
00484                 boost::filesystem::ofstream file_stream(file_path, mode);
00485                 file_stream.write(http_request_ptr->get_content(), http_request_ptr->get_content_length());
00486                 file_stream.close();
00487                 if (!boost::filesystem::exists(file_path)) {
00488                     static const std::string PUT_FAILED_HTML_START =
00489                         "<html><head>\n"
00490                         "<title>500 Server Error</title>\n"
00491                         "</head><body>\n"
00492                         "<h1>Server Error</h1>\n"
00493                         "<p>Error writing to ";
00494                     static const std::string PUT_FAILED_HTML_FINISH =
00495                         ".</p>\n"
00496                         "</body></html>\n";
00497                     writer->get_response().set_status_code(http::types::RESPONSE_CODE_SERVER_ERROR);
00498                     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_SERVER_ERROR);
00499                     writer->write_no_copy(PUT_FAILED_HTML_START);
00500                     writer << algorithm::xml_encode(http_request_ptr->get_resource());
00501                     writer->write_no_copy(PUT_FAILED_HTML_FINISH);
00502                 }
00503                 writer->send();
00504             } else if (http_request_ptr->get_method() == http::types::REQUEST_METHOD_DELETE) {
00505                 if (!boost::filesystem::exists(file_path)) {
00506                     sendNotFoundResponse(http_request_ptr, tcp_conn);
00507                 } else {
00508                     try {
00509                         boost::filesystem::remove(file_path);
00510                         writer->get_response().set_status_code(http::types::RESPONSE_CODE_NO_CONTENT);
00511                         writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NO_CONTENT);
00512                         writer->send();
00513                     } catch (std::exception& e) {
00514                         static const std::string DELETE_FAILED_HTML_START =
00515                             "<html><head>\n"
00516                             "<title>500 Server Error</title>\n"
00517                             "</head><body>\n"
00518                             "<h1>Server Error</h1>\n"
00519                             "<p>Could not delete ";
00520                         static const std::string DELETE_FAILED_HTML_FINISH =
00521                             ".</p>\n"
00522                             "</body></html>\n";
00523                         writer->get_response().set_status_code(http::types::RESPONSE_CODE_SERVER_ERROR);
00524                         writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_SERVER_ERROR);
00525                         writer->write_no_copy(DELETE_FAILED_HTML_START);
00526                         writer << algorithm::xml_encode(http_request_ptr->get_resource())
00527                             << ".</p><p>"
00528                             << boost::diagnostic_information(e);
00529                         writer->write_no_copy(DELETE_FAILED_HTML_FINISH);
00530                         writer->send();
00531                     }
00532                 }
00533             } else {
00534                 
00535                 writer->get_response().set_status_code(http::types::RESPONSE_CODE_SERVER_ERROR);
00536                 writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_SERVER_ERROR);
00537                 writer->send();
00538             }
00539         }
00540     }
00541     
00542     else {
00543         static const std::string NOT_IMPLEMENTED_HTML_START =
00544             "<html><head>\n"
00545             "<title>501 Not Implemented</title>\n"
00546             "</head><body>\n"
00547             "<h1>Not Implemented</h1>\n"
00548             "<p>The requested method ";
00549         static const std::string NOT_IMPLEMENTED_HTML_FINISH =
00550             " is not implemented on this server.</p>\n"
00551             "</body></html>\n";
00552         http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00553                                      boost::bind(&tcp::connection::finish, tcp_conn)));
00554         writer->get_response().set_status_code(http::types::RESPONSE_CODE_NOT_IMPLEMENTED);
00555         writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NOT_IMPLEMENTED);
00556         writer->write_no_copy(NOT_IMPLEMENTED_HTML_START);
00557         writer << algorithm::xml_encode(http_request_ptr->get_method());
00558         writer->write_no_copy(NOT_IMPLEMENTED_HTML_FINISH);
00559         writer->send();
00560     }
00561 }
00562 
00563 void FileService::sendNotFoundResponse(http::request_ptr& http_request_ptr,
00564                                        tcp::connection_ptr& tcp_conn)
00565 {
00566     static const std::string NOT_FOUND_HTML_START =
00567         "<html><head>\n"
00568         "<title>404 Not Found</title>\n"
00569         "</head><body>\n"
00570         "<h1>Not Found</h1>\n"
00571         "<p>The requested URL ";
00572     static const std::string NOT_FOUND_HTML_FINISH =
00573         " was not found on this server.</p>\n"
00574         "</body></html>\n";
00575     http::response_writer_ptr writer(http::response_writer::create(tcp_conn, *http_request_ptr,
00576                                  boost::bind(&tcp::connection::finish, tcp_conn)));
00577     writer->get_response().set_status_code(http::types::RESPONSE_CODE_NOT_FOUND);
00578     writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_NOT_FOUND);
00579     if (http_request_ptr->get_method() != http::types::REQUEST_METHOD_HEAD) {
00580         writer->write_no_copy(NOT_FOUND_HTML_START);
00581         writer << algorithm::xml_encode(http_request_ptr->get_resource());
00582         writer->write_no_copy(NOT_FOUND_HTML_FINISH);
00583     }
00584     writer->send();
00585 }
00586 
00587 void FileService::start(void)
00588 {
00589     PION_LOG_DEBUG(m_logger, "Starting up resource (" << get_resource() << ')');
00590 
00591     
00592     if (m_scan_setting != 0) {
00593         
00594         if (m_cache_setting == 0 && m_scan_setting > 1)
00595             m_cache_setting = 1;
00596 
00597         boost::mutex::scoped_lock cache_lock(m_cache_mutex);
00598 
00599         
00600         if (! m_file.empty()) {
00601             
00602             
00603             addCacheEntry("", m_file, m_scan_setting == 1);
00604         }
00605 
00606         
00607         if (! m_directory.empty())
00608             scanDirectory(m_directory);
00609     }
00610 }
00611 
00612 void FileService::stop(void)
00613 {
00614     PION_LOG_DEBUG(m_logger, "Shutting down resource (" << get_resource() << ')');
00615     
00616     boost::mutex::scoped_lock cache_lock(m_cache_mutex);
00617     m_cache_map.clear();
00618 }
00619 
00620 void FileService::scanDirectory(const boost::filesystem::path& dir_path)
00621 {
00622 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00623     PION_LOG_DEBUG(m_logger, "Scanning directory (" << get_resource() << "): "
00624                    << dir_path.string());
00625 #else
00626     PION_LOG_DEBUG(m_logger, "Scanning directory (" << get_resource() << "): "
00627                    << dir_path.directory_string());
00628 #endif
00629 
00630     
00631     boost::filesystem::directory_iterator end_itr;
00632     for ( boost::filesystem::directory_iterator itr( dir_path );
00633           itr != end_itr; ++itr )
00634     {
00635         if ( boost::filesystem::is_directory(*itr) ) {
00636             
00637 
00638             
00639             scanDirectory(*itr);
00640 
00641         } else {
00642             
00643 
00644             
00645 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00646             std::string file_path_string( itr->path().string() );
00647             std::string relative_path( file_path_string.substr(m_directory.string().size() + 1) );
00648 #else
00649             std::string file_path_string( itr->path().file_string() );
00650             std::string relative_path( file_path_string.substr(m_directory.directory_string().size() + 1) );
00651 #endif
00652 
00653             
00654             addCacheEntry(relative_path, *itr, m_scan_setting == 1);
00655         }
00656     }
00657 }
00658 
00659 std::pair<FileService::CacheMap::iterator, bool>
00660 FileService::addCacheEntry(const std::string& relative_path,
00661                            const boost::filesystem::path& file_path,
00662                            const bool placeholder)
00663 {
00664 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00665     DiskFile cache_entry(file_path, NULL, 0, 0, findMIMEType(file_path.filename().string()));
00666 #else
00667     DiskFile cache_entry(file_path, NULL, 0, 0, findMIMEType(file_path.leaf()));
00668 #endif
00669     if (! placeholder) {
00670         cache_entry.update();
00671         
00672         if (m_max_cache_size==0 || cache_entry.getFileSize() <= m_max_cache_size) {
00673             try { cache_entry.read(); }
00674             catch (std::exception&) {
00675 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00676                 PION_LOG_ERROR(m_logger, "Unable to add file to cache: "
00677                                << file_path.string());
00678 #else
00679                 PION_LOG_ERROR(m_logger, "Unable to add file to cache: "
00680                                << file_path.file_string());
00681 #endif
00682                 return std::make_pair(m_cache_map.end(), false);
00683             }
00684         }
00685     }
00686 
00687     std::pair<CacheMap::iterator, bool> add_entry_result
00688         = m_cache_map.insert( std::make_pair(relative_path, cache_entry) );
00689 
00690     if (add_entry_result.second) {
00691 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00692         PION_LOG_DEBUG(m_logger, "Added file to cache: "
00693                        << file_path.string());
00694 #else
00695         PION_LOG_DEBUG(m_logger, "Added file to cache: "
00696                        << file_path.file_string());
00697 #endif
00698     } else {
00699 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00700         PION_LOG_ERROR(m_logger, "Unable to insert cache entry for file: "
00701                        << file_path.string());
00702 #else
00703         PION_LOG_ERROR(m_logger, "Unable to insert cache entry for file: "
00704                        << file_path.file_string());
00705 #endif
00706     }
00707 
00708     return add_entry_result;
00709 }
00710 
00711 std::string FileService::findMIMEType(const std::string& file_name) {
00712     
00713     boost::call_once(FileService::createMIMETypes, m_mime_types_init_flag);
00714 
00715     
00716     std::string extension(file_name.substr(file_name.find_last_of('.') + 1));
00717     boost::algorithm::to_lower(extension);
00718 
00719     
00720     MIMETypeMap::iterator i = m_mime_types_ptr->find(extension);
00721     return (i == m_mime_types_ptr->end() ? DEFAULT_MIME_TYPE : i->second);
00722 }
00723 
00724 void FileService::createMIMETypes(void) {
00725     
00726     static MIMETypeMap mime_types;
00727 
00728     
00729     mime_types["js"] = "text/javascript";
00730     mime_types["txt"] = "text/plain";
00731     mime_types["xml"] = "text/xml";
00732     mime_types["css"] = "text/css";
00733     mime_types["htm"] = "text/html";
00734     mime_types["html"] = "text/html";
00735     mime_types["xhtml"] = "text/html";
00736     mime_types["gif"] = "image/gif";
00737     mime_types["png"] = "image/png";
00738     mime_types["jpg"] = "image/jpeg";
00739     mime_types["jpeg"] = "image/jpeg";
00740     mime_types["svg"] = "image/svg+xml";
00741     mime_types["eof"] = "application/vnd.ms-fontobject";
00742     mime_types["otf"] = "application/x-font-opentype";
00743     mime_types["ttf"] = "application/x-font-ttf";
00744     mime_types["woff"] = "application/font-woff";
00745     
00746 
00747     
00748     m_mime_types_ptr = &mime_types;
00749 }
00750 
00751 
00752 
00753 
00754 void DiskFile::update(void)
00755 {
00756     
00757     m_file_size = boost::numeric_cast<std::streamsize>(boost::filesystem::file_size( m_file_path ));
00758     m_last_modified = boost::filesystem::last_write_time( m_file_path );
00759     m_last_modified_string = http::types::get_date_string( m_last_modified );
00760 }
00761 
00762 void DiskFile::read(void)
00763 {
00764     
00765     m_file_content.reset(new char[m_file_size]);
00766 
00767     
00768     boost::filesystem::ifstream file_stream;
00769     file_stream.open(m_file_path, std::ios::in | std::ios::binary);
00770 
00771     
00772     if (!file_stream.is_open() || !file_stream.read(m_file_content.get(), m_file_size)) {
00773 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00774         const std::string file_name = m_file_path.string();
00775 #else
00776         const std::string file_name = m_file_path.file_string();
00777 #endif
00778         BOOST_THROW_EXCEPTION( error::read_file() << error::errinfo_file_name(file_name) );
00779     }
00780 }
00781 
00782 bool DiskFile::checkUpdated(void)
00783 {
00784     
00785     std::streamsize cur_size = boost::numeric_cast<std::streamsize>(boost::filesystem::file_size( m_file_path ));
00786     time_t cur_modified = boost::filesystem::last_write_time( m_file_path );
00787 
00788     
00789     if (cur_modified == m_last_modified && cur_size == m_file_size)
00790         return false;
00791 
00792     
00793 
00794     
00795     m_file_size = cur_size;
00796     m_last_modified = cur_modified;
00797     m_last_modified_string = http::types::get_date_string( m_last_modified );
00798 
00799     
00800     read();
00801 
00802     return true;
00803 }
00804 
00805 
00806 
00807 
00808 DiskFileSender::DiskFileSender(DiskFile& file, pion::http::request_ptr& http_request_ptr,
00809                                pion::tcp::connection_ptr& tcp_conn,
00810                                unsigned long max_chunk_size)
00811     : m_logger(PION_GET_LOGGER("pion.FileService.DiskFileSender")), m_disk_file(file),
00812     m_writer(pion::http::response_writer::create(tcp_conn, *http_request_ptr, boost::bind(&tcp::connection::finish, tcp_conn))),
00813     m_max_chunk_size(max_chunk_size), m_file_bytes_to_send(0), m_bytes_sent(0)
00814 {
00815 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00816     PION_LOG_DEBUG(m_logger, "Preparing to send file"
00817                    << (m_disk_file.hasFileContent() ? " (cached): " : ": ")
00818                    << m_disk_file.getFilePath().string());
00819 #else
00820     PION_LOG_DEBUG(m_logger, "Preparing to send file"
00821                    << (m_disk_file.hasFileContent() ? " (cached): " : ": ")
00822                    << m_disk_file.getFilePath().file_string());
00823 #endif
00824 
00825         
00826     m_writer->get_response().set_content_type(m_disk_file.getMimeType());
00827 
00828     
00829     m_writer->get_response().add_header(http::types::HEADER_LAST_MODIFIED,
00830                                       m_disk_file.getLastModifiedString());
00831 
00832     
00833     m_writer->get_response().set_status_code(http::types::RESPONSE_CODE_OK);
00834     m_writer->get_response().set_status_message(http::types::RESPONSE_MESSAGE_OK);
00835 }
00836 
00837 void DiskFileSender::send(void)
00838 {
00839     
00840     if (m_disk_file.getFileSize() <= m_bytes_sent) {
00841         m_writer->send();
00842         return;
00843     }
00844 
00845     
00846     m_file_bytes_to_send = m_disk_file.getFileSize() - m_bytes_sent;
00847     if (m_max_chunk_size > 0 && m_file_bytes_to_send > m_max_chunk_size)
00848         m_file_bytes_to_send = m_max_chunk_size;
00849 
00850     
00851     char *file_content_ptr;
00852 
00853     if (m_disk_file.hasFileContent()) {
00854 
00855         
00856         file_content_ptr = m_disk_file.getFileContent() + m_bytes_sent;
00857 
00858     } else {
00859         
00860 
00861         
00862         if (! m_file_stream.is_open()) {
00863             
00864             m_file_stream.open(m_disk_file.getFilePath(), std::ios::in | std::ios::binary);
00865             if (! m_file_stream.is_open()) {
00866 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00867                 PION_LOG_ERROR(m_logger, "Unable to open file: "
00868                                << m_disk_file.getFilePath().string());
00869 #else
00870                 PION_LOG_ERROR(m_logger, "Unable to open file: "
00871                                << m_disk_file.getFilePath().file_string());
00872 #endif
00873                 return;
00874             }
00875         }
00876 
00877         
00878         if (! m_content_buf) {
00879             
00880             m_content_buf.reset(new char[m_file_bytes_to_send]);
00881         }
00882         file_content_ptr = m_content_buf.get();
00883 
00884         
00885         if (! m_file_stream.read(m_content_buf.get(), m_file_bytes_to_send)) {
00886             if (m_file_stream.gcount() > 0) {
00887 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00888                 PION_LOG_ERROR(m_logger, "File size inconsistency: "
00889                                << m_disk_file.getFilePath().string());
00890 #else
00891                 PION_LOG_ERROR(m_logger, "File size inconsistency: "
00892                                << m_disk_file.getFilePath().file_string());
00893 #endif
00894             } else {
00895 # if defined(BOOST_FILESYSTEM_VERSION) && BOOST_FILESYSTEM_VERSION >= 3
00896                 PION_LOG_ERROR(m_logger, "Unable to read file: "
00897                                << m_disk_file.getFilePath().string());
00898 #else
00899                 PION_LOG_ERROR(m_logger, "Unable to read file: "
00900                                << m_disk_file.getFilePath().file_string());
00901 #endif
00902             }
00903             return;
00904         }
00905     }
00906 
00907     
00908     m_writer->write_no_copy(file_content_ptr, m_file_bytes_to_send);
00909 
00910     if (m_bytes_sent + m_file_bytes_to_send >= m_disk_file.getFileSize()) {
00911         
00912         if (m_bytes_sent > 0) {
00913             
00914             m_writer->send_final_chunk(boost::bind(&DiskFileSender::handle_write,
00915                                                  shared_from_this(),
00916                                                  boost::asio::placeholders::error,
00917                                                  boost::asio::placeholders::bytes_transferred));
00918         } else {
00919             
00920             m_writer->send(boost::bind(&DiskFileSender::handle_write,
00921                                        shared_from_this(),
00922                                        boost::asio::placeholders::error,
00923                                        boost::asio::placeholders::bytes_transferred));
00924         }
00925     } else {
00926         
00927         m_writer->send_chunk(boost::bind(&DiskFileSender::handle_write,
00928                                         shared_from_this(),
00929                                         boost::asio::placeholders::error,
00930                                         boost::asio::placeholders::bytes_transferred));
00931     }
00932 }
00933 
00934 void DiskFileSender::handle_write(const boost::system::error_code& write_error,
00935                                  std::size_t bytes_written)
00936 {
00937     bool finished_sending = true;
00938 
00939     if (write_error) {
00940         
00941         m_writer->get_connection()->set_lifecycle(tcp::connection::LIFECYCLE_CLOSE); 
00942         PION_LOG_WARN(m_logger, "Error sending file (" << write_error.message() << ')');
00943     } else {
00944         
00945 
00946         
00947         
00948         m_bytes_sent += m_file_bytes_to_send;
00949 
00950         if (m_bytes_sent >= m_disk_file.getFileSize()) {
00951             
00952             PION_LOG_DEBUG(m_logger, "Sent "
00953                            << (m_file_bytes_to_send < m_disk_file.getFileSize() ? "file chunk" : "complete file")
00954                            << " of " << m_file_bytes_to_send << " bytes (finished"
00955                            << (m_writer->get_connection()->get_keep_alive() ? ", keeping alive)" : ", closing)") );
00956         } else {
00957             
00958             PION_LOG_DEBUG(m_logger, "Sent file chunk of " << m_file_bytes_to_send << " bytes");
00959             finished_sending = false;
00960             m_writer->clear();
00961         }
00962     }
00963 
00964     if (finished_sending) {
00965         
00966         
00967         
00968         m_writer->get_connection()->finish();
00969     } else {
00970         send();
00971     }
00972 }
00973 
00974 
00975 }   
00976 }   
00977 
00978 
00980 extern "C" PION_PLUGIN pion::plugins::FileService *pion_create_FileService(void)
00981 {
00982     return new pion::plugins::FileService();
00983 }
00984 
00986 extern "C" PION_PLUGIN void pion_destroy_FileService(pion::plugins::FileService *service_ptr)
00987 {
00988     delete service_ptr;
00989 }