1 /*  $Id: ncfetch.cpp 617406 2020-09-30 19:09:56Z sadyrovr $
2  * ===========================================================================
3  *
4  *                            PUBLIC DOMAIN NOTICE
5  *               National Center for Biotechnology Information
6  *
7  *  This software/database is a "United States Government Work" under the
8  *  terms of the United States Copyright Act.  It was written as part of
9  *  the author's official duties as a United States Government employee and
10  *  thus cannot be copyrighted.  This software/database is freely available
11  *  to the public for use. The National Library of Medicine and the U.S.
12  *  Government have not placed any restriction on its use or reproduction.
13  *
14  *  Although all reasonable efforts have been taken to ensure the accuracy
15  *  and reliability of the software and data, the NLM and the U.S.
16  *  Government do not and cannot warrant the performance or results that
17  *  may be obtained by using this software or data. The NLM and the U.S.
18  *  Government disclaim all warranties, express or implied, including
19  *  warranties of performance, merchantability or fitness for any particular
20  *  purpose.
21  *
22  *  Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Authors:  Mike Dicuccio, Anatoliy Kuznetsov, Dmitry Kazimirov
27  *
28  * File Description:
29  *     BLOB (image) fetch from NetCache.
30  *     Takes three CGI arguments:
31  *       key=NETCACHE_KEY_OR_NETSTORAGE_LOCATOR
32  *       fmt=mime/type  (default: "image/png")
33  *       filename=Example.pdf
34  *
35  */
36 
37 #include <ncbi_pch.hpp>
38 
39 #include <cgi/cgiapp.hpp>
40 #include <cgi/cgictx.hpp>
41 #include <cgi/cgi_exception.hpp>
42 
43 #include <connect/services/netcache_api_expt.hpp>
44 #include <misc/netstorage/netstorage.hpp>
45 #include <connect/services/grid_app_version_info.hpp>
46 
47 #include <corelib/reader_writer.hpp>
48 #include <corelib/rwstream.hpp>
49 #include <corelib/ncbiargs.hpp>
50 
51 #define GRID_APP_NAME "ncfetch.cgi"
52 
53 USING_NCBI_SCOPE;
54 
55 #define NETSTORAGE_IO_BUFFER_SIZE (64 * 1024)
56 
57 /// NetCache BLOB/image fetch application
58 ///
59 class CNetCacheBlobFetchApp : public CCgiApplication
60 {
61 protected:
62     virtual int ProcessRequest(CCgiContext& ctx);
63     virtual int OnException(std::exception& e, CNcbiOstream& os);
64 
65 private:
66     string x_GetInitString(const string& key);
67 };
68 
x_GetInitString(const string & key)69 string CNetCacheBlobFetchApp::x_GetInitString(const string& key)
70 {
71     try {
72         if (CNetStorageObjectLoc(CCompoundIDPool(), key).HasSubKey()) {
73             return "mode=direct";
74         }
75     }
76     catch (...) {
77     }
78 
79     return GetConfig().Get("ncfetch", "netstorage");
80 }
81 
s_WriteHeader(const CCgiRequest & request,CCgiResponse & reply)82 void s_WriteHeader(const CCgiRequest& request, CCgiResponse& reply)
83 {
84     bool is_found;
85 
86     string fmt = request.GetEntry("fmt", &is_found);
87     if (fmt.empty() || !is_found)
88         fmt = "image/png";
89 
90     string filename(request.GetEntry("filename", &is_found));
91     if (is_found && !filename.empty()) {
92         string is_inline(request.GetEntry("inline", &is_found));
93 
94         reply.SetHeaderValue("Content-Disposition",
95                 (is_found && NStr::StringToBool(is_inline) ?
96                         "inline; filename=" : "attachment; filename=") +
97                 filename);
98     }
99 
100     reply.SetContentType(fmt);
101     reply.WriteHeader();
102 }
103 
ProcessRequest(CCgiContext & ctx)104 int CNetCacheBlobFetchApp::ProcessRequest(CCgiContext& ctx)
105 {
106     const CCgiRequest& request = ctx.GetRequest();
107     CCgiResponse&      reply   = ctx.GetResponse();
108 
109     bool is_found;
110 
111     string key = request.GetEntry("key", &is_found);
112     if (key.empty() || !is_found) {
113         NCBI_THROW(CArgException, eNoArg, "CGI entry 'key' is missing");
114     }
115 
116     CCombinedNetStorage netstorage(x_GetInitString(key));
117     CNetStorageObject netstorage_object(netstorage.Open(key));
118 
119     char buffer[NETSTORAGE_IO_BUFFER_SIZE];
120     size_t total_bytes_written = 0;
121 
122     while (!netstorage_object.Eof()) {
123         size_t bytes_read = netstorage_object.Read(buffer, sizeof(buffer));
124 
125         if (!bytes_read) continue;
126         if (!total_bytes_written) s_WriteHeader(request, reply);
127 
128         reply.out().write(buffer, bytes_read);
129         total_bytes_written += bytes_read;
130     }
131 
132     netstorage_object.Close();
133 
134     if (!total_bytes_written) s_WriteHeader(request, reply);
135     LOG_POST(Info << "retrieved data: " << total_bytes_written << " bytes");
136 
137     return 0;
138 }
139 
OnException(std::exception & e,CNcbiOstream & os)140 int CNetCacheBlobFetchApp::OnException(std::exception& e, CNcbiOstream& os)
141 {
142     string status_str;
143     string message;
144 
145     if (auto arg_exception = dynamic_cast<CArgException*>(&e)) {
146         status_str = "400 Bad Request";
147         message = arg_exception->GetMsg();
148         SetHTTPStatus(CRequestStatus::e400_BadRequest);
149     } else if (auto nc_exception = dynamic_cast<CNetStorageException*>(&e)) {
150         switch (nc_exception->GetErrCode()) {
151         case CNetStorageException::eAuthError:
152             status_str = "403 Forbidden";
153             message = nc_exception->GetMsg();
154             SetHTTPStatus(CRequestStatus::e403_Forbidden);
155             break;
156         case CNetStorageException::eNotExists:
157             status_str = "404 Not Found";
158             message = nc_exception->GetMsg();
159             SetHTTPStatus(CRequestStatus::e404_NotFound);
160             break;
161         default:
162             return CCgiApplication::OnException(e, os);
163         }
164     } else
165         return CCgiApplication::OnException(e, os);
166 
167     // Don't try to write to a broken output
168     if (!os.good()) {
169         return -1;
170     }
171 
172     try {
173         // HTTP header
174         os << "Status: " << status_str << HTTP_EOL <<
175                 "Content-Type: text/plain" HTTP_EOL HTTP_EOL <<
176                 "ERROR:  " << status_str << " " HTTP_EOL HTTP_EOL <<
177                 NStr::HtmlEncode(message) << HTTP_EOL;
178 
179         // Check for problems in sending the response
180         if (!os.good()) {
181             ERR_POST("CNetCacheBlobFetchApp::OnException() "
182                     "failed to send error page back to the client");
183             return -1;
184         }
185     }
186     catch (exception& ex) {
187         NCBI_REPORT_EXCEPTION("(CGI) CNetCacheBlobFetchApp::OnException", ex);
188     }
189 
190     return 0;
191 }
192 
main(int argc,const char * argv[])193 int main(int argc, const char* argv[])
194 {
195     GRID_APP_CHECK_VERSION_ARGS();
196 
197     SetSplitLogFile(true);
198     GetDiagContext().SetOldPostFormat(false);
199 
200     CNetCacheBlobFetchApp app;
201     return app.AppMain(argc, argv);
202 }
203