OLD | NEW |
| (Empty) |
1 // Copyright 2010 Google Inc. | |
2 // | |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
4 // you may not use this file except in compliance with the License. | |
5 // You may obtain a copy of the License at | |
6 // | |
7 // http://www.apache.org/licenses/LICENSE-2.0 | |
8 // | |
9 // Unless required by applicable law or agreed to in writing, software | |
10 // distributed under the License is distributed on an "AS IS" BASIS, | |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 // See the License for the specific language governing permissions and | |
13 // limitations under the License. | |
14 // ======================================================================== | |
15 | |
16 #include "omaha/goopdate/update_response_utils.h" | |
17 #include "omaha/base/debug.h" | |
18 #include "omaha/base/error.h" | |
19 #include "omaha/base/logging.h" | |
20 #include "omaha/common/lang.h" | |
21 #include "omaha/common/experiment_labels.h" | |
22 #include "omaha/goopdate/model.h" | |
23 #include "omaha/goopdate/server_resource.h" | |
24 #include "omaha/goopdate/string_formatter.h" | |
25 | |
26 namespace omaha { | |
27 | |
28 namespace update_response_utils { | |
29 | |
30 // TODO(omaha): unit test the functions below. | |
31 | |
32 // Returns a pointer to the app object corresponding to the appid in the | |
33 // response object. Returns NULL if the app is not found. | |
34 const xml::response::App* GetApp(const xml::response::Response& response, | |
35 const CString& appid) { | |
36 size_t app_index = 0; | |
37 for (; app_index < response.apps.size(); ++app_index) { | |
38 if (!appid.CompareNoCase(response.apps[app_index].appid)) { | |
39 return &response.apps[app_index]; | |
40 } | |
41 } | |
42 return NULL; | |
43 } | |
44 | |
45 HRESULT GetInstallData(const std::vector<xml::response::Data>& data, | |
46 const CString& install_data_index, | |
47 CString* install_data) { | |
48 ASSERT1(install_data); | |
49 if (install_data_index.IsEmpty()) { | |
50 return S_OK; | |
51 } | |
52 | |
53 std::vector<xml::response::Data>::const_iterator it; | |
54 for (it = data.begin(); it != data.end(); ++it) { | |
55 if (install_data_index != it->install_data_index) { | |
56 continue; | |
57 } | |
58 | |
59 if (it->status != kResponseStatusOkValue) { | |
60 ASSERT1(it->status == kResponseDataStatusNoData); | |
61 return GOOPDATE_E_INVALID_INSTALL_DATA_INDEX; | |
62 } | |
63 | |
64 ASSERT1(!it->install_data.IsEmpty()); | |
65 *install_data = it->install_data; | |
66 return S_OK; | |
67 } | |
68 | |
69 return GOOPDATE_E_INVALID_INSTALL_DATA_INDEX; | |
70 } | |
71 | |
72 // Check the outer elements first, then check any child elements only if the | |
73 // outer element was successful. | |
74 CString GetAppResponseStatus(const xml::response::App& app) { | |
75 if (_tcsicmp(kResponseStatusOkValue, app.status) != 0) { | |
76 ASSERT1(!app.status.IsEmpty()); | |
77 return app.status; | |
78 } | |
79 | |
80 if (!app.update_check.status.IsEmpty() && | |
81 _tcsicmp(kResponseStatusOkValue, app.update_check.status) != 0) { | |
82 return app.update_check.status; | |
83 } | |
84 | |
85 std::vector<xml::response::Data>::const_iterator data; | |
86 for (data = app.data.begin(); data != app.data.end(); ++data) { | |
87 if (!data->status.IsEmpty() && | |
88 _tcsicmp(kResponseStatusOkValue, data->status) != 0) { | |
89 return data->status; | |
90 } | |
91 } | |
92 | |
93 if (!app.ping.status.IsEmpty() && | |
94 _tcsicmp(kResponseStatusOkValue, app.ping.status) != 0) { | |
95 return app.ping.status; | |
96 } | |
97 | |
98 std::vector<xml::response::Event>::const_iterator it; | |
99 for (it = app.events.begin(); it != app.events.end(); ++it) { | |
100 if (!it->status.IsEmpty() && | |
101 _tcsicmp(kResponseStatusOkValue, it->status) != 0) { | |
102 return it->status; | |
103 } | |
104 } | |
105 | |
106 // TODO(omaha): verify that no other elements can report errors | |
107 // once we've finalized the protocol. | |
108 | |
109 return app.status; | |
110 } | |
111 | |
112 HRESULT BuildApp(const xml::UpdateResponse* update_response, | |
113 HRESULT code, | |
114 App* app) { | |
115 ASSERT1(update_response); | |
116 ASSERT1(SUCCEEDED(code) || code == GOOPDATE_E_NO_UPDATE_RESPONSE); | |
117 ASSERT1(app); | |
118 | |
119 AppVersion* next_version = app->next_version(); | |
120 | |
121 const CString& app_id = app->app_guid_string(); | |
122 | |
123 const xml::response::App* response_app(GetApp(update_response->response(), | |
124 app_id)); | |
125 ASSERT1(response_app); | |
126 const xml::response::UpdateCheck& update_check = response_app->update_check; | |
127 | |
128 VERIFY1(SUCCEEDED(app->put_ttToken(CComBSTR(update_check.tt_token)))); | |
129 | |
130 if (code == GOOPDATE_E_NO_UPDATE_RESPONSE) { | |
131 return S_OK; | |
132 } | |
133 | |
134 for (size_t i = 0; i < update_check.urls.size(); ++i) { | |
135 HRESULT hr = next_version->AddDownloadBaseUrl(update_check.urls[i]); | |
136 if (FAILED(hr)) { | |
137 return hr; | |
138 } | |
139 } | |
140 | |
141 for (size_t i = 0; i < update_check.install_manifest.packages.size(); ++i) { | |
142 const xml::InstallPackage& package( | |
143 update_check.install_manifest.packages[i]); | |
144 HRESULT hr = next_version->AddPackage(package.name, | |
145 package.size, | |
146 package.hash); | |
147 if (FAILED(hr)) { | |
148 return hr; | |
149 } | |
150 } | |
151 | |
152 CString server_install_data; | |
153 HRESULT hr = GetInstallData(response_app->data, | |
154 app->server_install_data_index(), | |
155 &server_install_data); | |
156 if (FAILED(hr)) { | |
157 return hr; | |
158 } | |
159 app->set_server_install_data(server_install_data); | |
160 | |
161 ASSERT1(!next_version->install_manifest()); | |
162 next_version->set_install_manifest( | |
163 new xml::InstallManifest(update_check.install_manifest)); | |
164 | |
165 // TODO(omaha): it appears the version_ below holds either the manifest | |
166 // version or the "pv" version, written by the installer. If this is the case, | |
167 // then it is confusing and perhaps we need to have two different members to | |
168 // hold these values. | |
169 ASSERT1(next_version->version().IsEmpty()); | |
170 next_version->set_version(next_version->install_manifest()->version); | |
171 | |
172 return S_OK; | |
173 } | |
174 | |
175 // "noupdate" is an error for fresh installs, but a successful completion in | |
176 // the cases of silent and on demand updates. The caller is responsible for | |
177 // interpreting "noupdate" as it sees fit. | |
178 xml::UpdateResponseResult GetResult(const xml::UpdateResponse* update_response, | |
179 const CString& appid, | |
180 const CString& language) { | |
181 ASSERT1(update_response); | |
182 const xml::response::App* response_app(GetApp(update_response->response(), | |
183 appid)); | |
184 | |
185 StringFormatter formatter(language); | |
186 CString text; | |
187 | |
188 if (!response_app) { | |
189 CORE_LOG(L1, (_T("[UpdateResponse::GetResult][app not found][%s]"), appid)); | |
190 VERIFY1(SUCCEEDED(formatter.LoadString(IDS_UNKNOWN_APPLICATION, &text))); | |
191 return std::make_pair(GOOPDATE_E_NO_SERVER_RESPONSE, text); | |
192 } | |
193 | |
194 const xml::response::UpdateCheck& update_check = response_app->update_check; | |
195 const CString& status = GetAppResponseStatus(*response_app); | |
196 const CString& display_name = update_check.install_manifest.name; | |
197 | |
198 ASSERT1(!status.IsEmpty()); | |
199 CORE_LOG(L1, (_T("[UpdateResponse::GetResult][%s][%s][%s]"), | |
200 appid, status, display_name)); | |
201 | |
202 // ok | |
203 if (_tcsicmp(kResponseStatusOkValue, status) == 0) { | |
204 return std::make_pair(S_OK, CString()); | |
205 } | |
206 | |
207 // noupdate | |
208 if (_tcsicmp(kResponseStatusNoUpdate, status) == 0) { | |
209 VERIFY1(SUCCEEDED(formatter.LoadString(IDS_NO_UPDATE_RESPONSE, &text))); | |
210 return std::make_pair(GOOPDATE_E_NO_UPDATE_RESPONSE, text); | |
211 } | |
212 | |
213 // "restricted" | |
214 if (_tcsicmp(kResponseStatusRestrictedExportCountry, status) == 0) { | |
215 VERIFY1(SUCCEEDED(formatter.LoadString(IDS_RESTRICTED_RESPONSE_FROM_SERVER, | |
216 &text))); | |
217 return std::make_pair(GOOPDATE_E_RESTRICTED_SERVER_RESPONSE, text); | |
218 } | |
219 | |
220 // "error-UnKnownApplication" | |
221 if (_tcsicmp(kResponseStatusUnKnownApplication, status) == 0) { | |
222 VERIFY1(SUCCEEDED(formatter.LoadString(IDS_UNKNOWN_APPLICATION, &text))); | |
223 return std::make_pair(GOOPDATE_E_UNKNOWN_APP_SERVER_RESPONSE, text); | |
224 } | |
225 | |
226 // "error-OsNotSupported" | |
227 if (_tcsicmp(kResponseStatusOsNotSupported, status) == 0) { | |
228 const CString& error_url(update_check.error_url); | |
229 VERIFY1(SUCCEEDED(formatter.LoadString(IDS_OS_NOT_SUPPORTED, &text))); | |
230 if (!error_url.IsEmpty()) { | |
231 // TODO(omaha3): The error URL is no longer in the error string. Either | |
232 // we need to provide this URL to the client or we need to deprecate | |
233 // error_url and put this information in the Get Help redirect. | |
234 // Alternatively, we could have the COM server build error URLs in all | |
235 // cases. The current UI would still need to build an URL for the entire | |
236 // bundle, though, because it does not have per-app links. | |
237 } | |
238 return std::make_pair(GOOPDATE_E_OS_NOT_SUPPORTED, text); | |
239 } | |
240 | |
241 // "error-internal" | |
242 if (_tcsicmp(kResponseStatusInternalError, status) == 0) { | |
243 VERIFY1(SUCCEEDED(formatter.FormatMessage(&text, | |
244 IDS_NON_OK_RESPONSE_FROM_SERVER, | |
245 status))); | |
246 return std::make_pair(GOOPDATE_E_INTERNAL_ERROR_SERVER_RESPONSE, text); | |
247 } | |
248 | |
249 // "error-hash" | |
250 if (_tcsicmp(kResponseStatusHashError, status) == 0) { | |
251 VERIFY1(SUCCEEDED(formatter.FormatMessage(&text, | |
252 IDS_NON_OK_RESPONSE_FROM_SERVER, | |
253 status))); | |
254 return std::make_pair(GOOPDATE_E_SERVER_RESPONSE_NO_HASH, text); | |
255 } | |
256 | |
257 // "error-unsupportedprotocol" | |
258 if (_tcsicmp(kResponseStatusUnsupportedProtocol, status) == 0) { | |
259 // TODO(omaha): Ideally, we would provide an app-specific URL instead of | |
260 // just the publisher name. If it was a link, we could use point to a | |
261 // redirect URL and provide the app GUID rather than somehow obtaining the | |
262 // app-specific URL. | |
263 VERIFY1(SUCCEEDED(formatter.FormatMessage(&text, | |
264 IDS_INSTALLER_OLD, | |
265 kShortCompanyName))); | |
266 return std::make_pair(GOOPDATE_E_SERVER_RESPONSE_UNSUPPORTED_PROTOCOL, | |
267 text); | |
268 } | |
269 | |
270 VERIFY1(SUCCEEDED(formatter.FormatMessage(&text, | |
271 IDS_NON_OK_RESPONSE_FROM_SERVER, | |
272 status))); | |
273 return std::make_pair(GOOPDATE_E_UNKNOWN_SERVER_RESPONSE, text); | |
274 } | |
275 | |
276 bool IsOmahaUpdateAvailable(const xml::UpdateResponse* update_response) { | |
277 ASSERT1(update_response); | |
278 xml::UpdateResponseResult update_response_result( | |
279 update_response_utils::GetResult(update_response, | |
280 kGoogleUpdateAppId, | |
281 lang::GetDefaultLanguage(true))); | |
282 return update_response_result.first == S_OK; | |
283 } | |
284 | |
285 HRESULT ApplyExperimentLabelDeltas(bool is_machine, | |
286 const xml::UpdateResponse* update_response) { | |
287 ASSERT1(update_response); | |
288 | |
289 for (size_t app_index = 0; | |
290 app_index < update_response->response().apps.size(); | |
291 ++app_index) { | |
292 const xml::response::App& app = update_response->response().apps[app_index]; | |
293 if (!app.experiments.IsEmpty()) { | |
294 VERIFY1(IsGuid(app.appid)); | |
295 | |
296 ExperimentLabels labels; | |
297 VERIFY1(SUCCEEDED(labels.ReadFromRegistry(is_machine, app.appid))); | |
298 | |
299 if (!labels.DeserializeAndApplyDelta(app.experiments)) { | |
300 return E_FAIL; | |
301 } | |
302 | |
303 HRESULT hr = labels.WriteToRegistry(is_machine, app.appid); | |
304 if (FAILED(hr)) { | |
305 return hr; | |
306 } | |
307 } | |
308 } | |
309 | |
310 return S_OK; | |
311 } | |
312 | |
313 } // namespace update_response_utils | |
314 | |
315 } // namespace omaha | |
316 | |
OLD | NEW |