1 /*
2  * SessionPanmirrorCrossref.cpp
3  *
4  * Copyright (C) 2009-16 by RStudio, Inc.
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 
16 #include "SessionPanmirrorCrossref.hpp"
17 
18 #include <shared_core/Error.hpp>
19 #include <shared_core/json/Json.hpp>
20 
21 #include <core/Exec.hpp>
22 #include <core/json/JsonRpc.hpp>
23 #include <core/http/Util.hpp>
24 
25 #include <r/ROptions.hpp>
26 
27 #include <session/SessionModuleContext.hpp>
28 #include <session/SessionAsyncDownloadFile.hpp>
29 
30 using namespace rstudio::core;
31 
32 namespace rstudio {
33 namespace session {
34 namespace modules {
35 namespace panmirror {
36 namespace crossref {
37 
38 namespace {
39 
crossrefContentRequestHandler(const core::json::Value & value,core::json::JsonRpcResponse * pResponse)40 void crossrefContentRequestHandler(const core::json::Value& value, core::json::JsonRpcResponse* pResponse)
41 {
42    pResponse->setResult(value);
43 }
44 
crossrefApiRequestHandler(const core::json::Value & value,core::json::JsonRpcResponse * pResponse)45 void crossrefApiRequestHandler(const core::json::Value& value, core::json::JsonRpcResponse* pResponse)
46 {
47    if (json::isType<json::Object>(value))
48    {
49       json::Object responseJson = value.getObject();
50       std::string status;
51       json::Object message;
52       Error error = json::readObject(responseJson, "status", status,
53                                                    "message", message);
54       if (error)
55       {
56          json::setErrorResponse(error, pResponse);
57       }
58       else if (status != "ok")
59       {
60          Error error = systemError(boost::system::errc::state_not_recoverable,
61                                    "Unexpected status from crossref api: " + status,
62                                    ERROR_LOCATION);
63          json::setErrorResponse(error, pResponse);
64       }
65       else
66       {
67          pResponse->setResult(message);
68       }
69    }
70    else
71    {
72       Error error = systemError(boost::system::errc::state_not_recoverable,
73                                 "Unexpected response from crossref api",
74                                 ERROR_LOCATION);
75       json::setErrorResponse(error, pResponse);
76    }
77 }
78 
crossrefRequest(const std::string & resource,const http::Fields & params,const session::JsonRpcResponseHandler & handler,const json::JsonRpcFunctionContinuation & cont)79 void crossrefRequest(const std::string& resource,
80                      const http::Fields& params,
81                      const session::JsonRpcResponseHandler& handler,
82                      const json::JsonRpcFunctionContinuation& cont)
83 {
84    // email address
85    std::string email = r::options::getOption<std::string>("rstudio.crossref_email",
86                                                           "crossref@rstudio.com", false);
87 
88    // build user agent
89    std::string userAgent = r::options::getOption<std::string>("HTTPUserAgent", "RStudio") +
90                            "; RStudio Crossref Cite (mailto:" + email + ")";
91 
92    // build query string
93    std::string queryString;
94    core::http::util::buildQueryString(params, &queryString);
95    if (queryString.length() > 0)
96       queryString = "?" + queryString;
97 
98    // build the url and make the request
99    boost::format fmt("%s/%s%s");
100    const std::string url = boost::str(fmt % kCrossrefApiHost % resource % queryString);
101 
102    http::Fields headers;
103    asyncJsonRpcRequest(url, userAgent, headers, handler, cont);
104 }
105 
106 
crossrefWorks(const json::JsonRpcRequest & request,const json::JsonRpcFunctionContinuation & cont)107 void crossrefWorks(const json::JsonRpcRequest& request,
108                    const json::JsonRpcFunctionContinuation& cont)
109 {
110    // extract query
111    std::string query;
112    Error error = json::readParams(request.params, &query);
113    if (error)
114    {
115      json::JsonRpcResponse response;
116      setErrorResponse(error, &response);
117      cont(Success(), &response);
118      return;
119    }
120 
121    // build params
122    core::http::Fields params;
123    params.push_back(std::make_pair("query", query));
124 
125    // make the request
126    crossrefRequest(kCrossrefWorks, params, crossrefApiRequestHandler, cont);
127 }
128 
crossrefDoi(const json::JsonRpcRequest & request,const json::JsonRpcFunctionContinuation & cont)129 void crossrefDoi(const json::JsonRpcRequest& request,
130                  const json::JsonRpcFunctionContinuation& cont)
131 {
132     std::string doi;
133     Error error = json::readParams(request.params, &doi);
134     if (error)
135     {
136       json::JsonRpcResponse response;
137       setErrorResponse(error, &response);
138       cont(Success(), &response);
139       return;
140     }
141 
142     // Path to DOI metadata works/{doi}/transform/{format} (see: https://citation.crosscite.org/docs.html#sec-5)
143     const char * const kCitationFormat = "application/vnd.citationstyles.csl+json";
144     boost::format fmt("%s/%s/transform/%s");
145     const std::string resourcePath = boost::str(fmt % kCrossrefWorks % doi % kCitationFormat);
146 
147     // No parameters
148     core::http::Fields params;
149 
150     // make the request
151     crossrefRequest(resourcePath, params, crossrefContentRequestHandler, cont);
152 }
153 
154 
155 } // end anonymous namespace
156 
initialize()157 Error initialize()
158 {
159    ExecBlock initBlock;
160    initBlock.addFunctions()
161       (boost::bind(module_context::registerAsyncRpcMethod, "crossref_works", crossrefWorks))
162       (boost::bind(module_context::registerAsyncRpcMethod, "crossref_doi", crossrefDoi))
163    ;
164    return initBlock.execute();
165 }
166 
167 } // end namespace crossref
168 } // end namespace panmirror
169 } // end namespace modules
170 } // end namespace session
171 } // end namespace rstudio
172