1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 //
5 // A library to manage RLZ information for access-points shared
6 // across different client applications.
7
8 #include "rlz/lib/rlz_lib.h"
9
10 #include "base/strings/string_util.h"
11 #include "base/strings/stringprintf.h"
12 #include "rlz/lib/assert.h"
13 #include "rlz/lib/crc32.h"
14 #include "rlz/lib/financial_ping.h"
15 #include "rlz/lib/lib_values.h"
16 #include "rlz/lib/rlz_value_store.h"
17 #include "rlz/lib/string_utils.h"
18
19 namespace {
20
21 // Event information returned from ping response.
22 struct ReturnedEvent {
23 rlz_lib::AccessPoint access_point;
24 rlz_lib::Event event_type;
25 };
26
27 // Helper functions
28
IsAccessPointSupported(rlz_lib::AccessPoint point)29 bool IsAccessPointSupported(rlz_lib::AccessPoint point) {
30 switch (point) {
31 case rlz_lib::NO_ACCESS_POINT:
32 case rlz_lib::LAST_ACCESS_POINT:
33
34 case rlz_lib::MOBILE_IDLE_SCREEN_BLACKBERRY:
35 case rlz_lib::MOBILE_IDLE_SCREEN_WINMOB:
36 case rlz_lib::MOBILE_IDLE_SCREEN_SYMBIAN:
37 // These AP's are never available on Windows PCs.
38 return false;
39
40 case rlz_lib::IE_DEFAULT_SEARCH:
41 case rlz_lib::IE_HOME_PAGE:
42 case rlz_lib::IETB_SEARCH_BOX:
43 case rlz_lib::QUICK_SEARCH_BOX:
44 case rlz_lib::GD_DESKBAND:
45 case rlz_lib::GD_SEARCH_GADGET:
46 case rlz_lib::GD_WEB_SERVER:
47 case rlz_lib::GD_OUTLOOK:
48 case rlz_lib::CHROME_OMNIBOX:
49 case rlz_lib::CHROME_HOME_PAGE:
50 // TODO: Figure out when these settings are set to Google.
51
52 default:
53 return true;
54 }
55 }
56
57 // Current RLZ can only use [a-zA-Z0-9_\-]
58 // We will be more liberal and allow some additional chars, but not url meta
59 // chars.
IsGoodRlzChar(const char ch)60 bool IsGoodRlzChar(const char ch) {
61 if (IsAsciiAlpha(ch) || IsAsciiDigit(ch))
62 return true;
63
64 switch (ch) {
65 case '_':
66 case '-':
67 case '!':
68 case '@':
69 case '$':
70 case '*':
71 case '(':
72 case ')':
73 case ';':
74 case '.':
75 case '<':
76 case '>':
77 return true;
78 }
79
80 return false;
81 }
82
83 // This function will remove bad rlz chars and also limit the max rlz to some
84 // reasonable size. It also assumes that normalized_rlz is at least
85 // kMaxRlzLength+1 long.
NormalizeRlz(const char * raw_rlz,char * normalized_rlz)86 void NormalizeRlz(const char* raw_rlz, char* normalized_rlz) {
87 size_t index = 0;
88 for (; raw_rlz[index] != 0 && index < rlz_lib::kMaxRlzLength; ++index) {
89 char current = raw_rlz[index];
90 if (IsGoodRlzChar(current)) {
91 normalized_rlz[index] = current;
92 } else {
93 normalized_rlz[index] = '.';
94 }
95 }
96
97 normalized_rlz[index] = 0;
98 }
99
GetEventsFromResponseString(const std::string & response_line,const std::string & field_header,std::vector<ReturnedEvent> * event_array)100 void GetEventsFromResponseString(
101 const std::string& response_line,
102 const std::string& field_header,
103 std::vector<ReturnedEvent>* event_array) {
104 // Get the string of events.
105 std::string events = response_line.substr(field_header.size());
106 base::TrimWhitespaceASCII(events, base::TRIM_LEADING, &events);
107
108 int events_length = events.find_first_of("\r\n ");
109 if (events_length < 0)
110 events_length = events.size();
111 events = events.substr(0, events_length);
112
113 // Break this up into individual events
114 int event_end_index = -1;
115 do {
116 int event_begin = event_end_index + 1;
117 event_end_index = events.find(rlz_lib::kEventsCgiSeparator, event_begin);
118 int event_end = event_end_index;
119 if (event_end < 0)
120 event_end = events_length;
121
122 std::string event_string = events.substr(event_begin,
123 event_end - event_begin);
124 if (event_string.size() != 3) // 3 = 2(AP) + 1(E)
125 continue;
126
127 rlz_lib::AccessPoint point = rlz_lib::NO_ACCESS_POINT;
128 rlz_lib::Event event = rlz_lib::INVALID_EVENT;
129 if (!GetAccessPointFromName(event_string.substr(0, 2).c_str(), &point) ||
130 point == rlz_lib::NO_ACCESS_POINT) {
131 continue;
132 }
133
134 if (!GetEventFromName(event_string.substr(event_string.size() - 1).c_str(),
135 &event) || event == rlz_lib::INVALID_EVENT) {
136 continue;
137 }
138
139 ReturnedEvent current_event = {point, event};
140 event_array->push_back(current_event);
141 } while (event_end_index >= 0);
142 }
143
144 // Event storage functions.
RecordStatefulEvent(rlz_lib::Product product,rlz_lib::AccessPoint point,rlz_lib::Event event)145 bool RecordStatefulEvent(rlz_lib::Product product, rlz_lib::AccessPoint point,
146 rlz_lib::Event event) {
147 rlz_lib::ScopedRlzValueStoreLock lock;
148 rlz_lib::RlzValueStore* store = lock.GetStore();
149 if (!store || !store->HasAccess(rlz_lib::RlzValueStore::kWriteAccess))
150 return false;
151
152 // Write the new event to the value store.
153 const char* point_name = GetAccessPointName(point);
154 const char* event_name = GetEventName(event);
155 if (!point_name || !event_name)
156 return false;
157
158 if (!point_name[0] || !event_name[0])
159 return false;
160
161 std::string new_event_value;
162 base::StringAppendF(&new_event_value, "%s%s", point_name, event_name);
163 return store->AddStatefulEvent(product, new_event_value.c_str());
164 }
165
GetProductEventsAsCgiHelper(rlz_lib::Product product,char * cgi,size_t cgi_size,rlz_lib::RlzValueStore * store)166 bool GetProductEventsAsCgiHelper(rlz_lib::Product product, char* cgi,
167 size_t cgi_size,
168 rlz_lib::RlzValueStore* store) {
169 // Prepend the CGI param key to the buffer.
170 std::string cgi_arg;
171 base::StringAppendF(&cgi_arg, "%s=", rlz_lib::kEventsCgiVariable);
172 if (cgi_size <= cgi_arg.size())
173 return false;
174
175 size_t index;
176 for (index = 0; index < cgi_arg.size(); ++index)
177 cgi[index] = cgi_arg[index];
178
179 // Read stored events.
180 std::vector<std::string> events;
181 if (!store->ReadProductEvents(product, &events))
182 return false;
183
184 // Append the events to the buffer.
185 size_t num_values = 0;
186
187 for (num_values = 0; num_values < events.size(); ++num_values) {
188 cgi[index] = '\0';
189
190 int divider = num_values > 0 ? 1 : 0;
191 int size = cgi_size - (index + divider);
192 if (size <= 0)
193 return cgi_size >= (rlz_lib::kMaxCgiLength + 1);
194
195 strncpy(cgi + index + divider, events[num_values].c_str(), size);
196 if (divider)
197 cgi[index] = rlz_lib::kEventsCgiSeparator;
198
199 index += std::min((int)events[num_values].length(), size) + divider;
200 }
201
202 cgi[index] = '\0';
203
204 return num_values > 0;
205 }
206
207 } // namespace
208
209 namespace rlz_lib {
210
211 #if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
SetURLRequestContext(net::URLRequestContextGetter * context)212 bool SetURLRequestContext(net::URLRequestContextGetter* context) {
213 return FinancialPing::SetURLRequestContext(context);
214 }
215 #endif
216
GetProductEventsAsCgi(Product product,char * cgi,size_t cgi_size)217 bool GetProductEventsAsCgi(Product product, char* cgi, size_t cgi_size) {
218 if (!cgi || cgi_size <= 0) {
219 ASSERT_STRING("GetProductEventsAsCgi: Invalid buffer");
220 return false;
221 }
222
223 cgi[0] = 0;
224
225 ScopedRlzValueStoreLock lock;
226 RlzValueStore* store = lock.GetStore();
227 if (!store || !store->HasAccess(RlzValueStore::kReadAccess))
228 return false;
229
230 size_t size_local = std::min(
231 static_cast<size_t>(kMaxCgiLength + 1), cgi_size);
232 bool result = GetProductEventsAsCgiHelper(product, cgi, size_local, store);
233
234 if (!result) {
235 ASSERT_STRING("GetProductEventsAsCgi: Possibly insufficient buffer size");
236 cgi[0] = 0;
237 return false;
238 }
239
240 return true;
241 }
242
RecordProductEvent(Product product,AccessPoint point,Event event)243 bool RecordProductEvent(Product product, AccessPoint point, Event event) {
244 ScopedRlzValueStoreLock lock;
245 RlzValueStore* store = lock.GetStore();
246 if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
247 return false;
248
249 // Get this event's value.
250 const char* point_name = GetAccessPointName(point);
251 const char* event_name = GetEventName(event);
252 if (!point_name || !event_name)
253 return false;
254
255 if (!point_name[0] || !event_name[0])
256 return false;
257
258 std::string new_event_value;
259 base::StringAppendF(&new_event_value, "%s%s", point_name, event_name);
260
261 // Check whether this event is a stateful event. If so, don't record it.
262 if (store->IsStatefulEvent(product, new_event_value.c_str())) {
263 // For a stateful event we skip recording, this function is also
264 // considered successful.
265 return true;
266 }
267
268 // Write the new event to the value store.
269 return store->AddProductEvent(product, new_event_value.c_str());
270 }
271
ClearProductEvent(Product product,AccessPoint point,Event event)272 bool ClearProductEvent(Product product, AccessPoint point, Event event) {
273 ScopedRlzValueStoreLock lock;
274 RlzValueStore* store = lock.GetStore();
275 if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
276 return false;
277
278 // Get the event's value store value and delete it.
279 const char* point_name = GetAccessPointName(point);
280 const char* event_name = GetEventName(event);
281 if (!point_name || !event_name)
282 return false;
283
284 if (!point_name[0] || !event_name[0])
285 return false;
286
287 std::string event_value;
288 base::StringAppendF(&event_value, "%s%s", point_name, event_name);
289 return store->ClearProductEvent(product, event_value.c_str());
290 }
291
292 // RLZ storage functions.
293
GetAccessPointRlz(AccessPoint point,char * rlz,size_t rlz_size)294 bool GetAccessPointRlz(AccessPoint point, char* rlz, size_t rlz_size) {
295 if (!rlz || rlz_size <= 0) {
296 ASSERT_STRING("GetAccessPointRlz: Invalid buffer");
297 return false;
298 }
299
300 rlz[0] = 0;
301
302 ScopedRlzValueStoreLock lock;
303 RlzValueStore* store = lock.GetStore();
304 if (!store || !store->HasAccess(RlzValueStore::kReadAccess))
305 return false;
306
307 if (!IsAccessPointSupported(point))
308 return false;
309
310 return store->ReadAccessPointRlz(point, rlz, rlz_size);
311 }
312
SetAccessPointRlz(AccessPoint point,const char * new_rlz)313 bool SetAccessPointRlz(AccessPoint point, const char* new_rlz) {
314 ScopedRlzValueStoreLock lock;
315 RlzValueStore* store = lock.GetStore();
316 if (!store || !store->HasAccess(RlzValueStore::kWriteAccess))
317 return false;
318
319 if (!new_rlz) {
320 ASSERT_STRING("SetAccessPointRlz: Invalid buffer");
321 return false;
322 }
323
324 // Return false if the access point is not set to Google.
325 if (!IsAccessPointSupported(point)) {
326 ASSERT_STRING(("SetAccessPointRlz: "
327 "Cannot set RLZ for unsupported access point."));
328 return false;
329 }
330
331 // Verify the RLZ length.
332 size_t rlz_length = strlen(new_rlz);
333 if (rlz_length > kMaxRlzLength) {
334 ASSERT_STRING("SetAccessPointRlz: RLZ length is exceeds max allowed.");
335 return false;
336 }
337
338 char normalized_rlz[kMaxRlzLength + 1];
339 NormalizeRlz(new_rlz, normalized_rlz);
340 VERIFY(strlen(new_rlz) == rlz_length);
341
342 // Setting RLZ to empty == clearing.
343 if (normalized_rlz[0] == 0)
344 return store->ClearAccessPointRlz(point);
345 return store->WriteAccessPointRlz(point, normalized_rlz);
346 }
347
348 // Financial Server pinging functions.
349
FormFinancialPingRequest(Product product,const AccessPoint * access_points,const char * product_signature,const char * product_brand,const char * product_id,const char * product_lang,bool exclude_machine_id,char * request,size_t request_buffer_size)350 bool FormFinancialPingRequest(Product product, const AccessPoint* access_points,
351 const char* product_signature,
352 const char* product_brand,
353 const char* product_id,
354 const char* product_lang,
355 bool exclude_machine_id,
356 char* request, size_t request_buffer_size) {
357 if (!request || request_buffer_size == 0)
358 return false;
359
360 request[0] = 0;
361
362 std::string request_string;
363 if (!FinancialPing::FormRequest(product, access_points, product_signature,
364 product_brand, product_id, product_lang,
365 exclude_machine_id, &request_string))
366 return false;
367
368 if (request_string.size() >= request_buffer_size)
369 return false;
370
371 strncpy(request, request_string.c_str(), request_buffer_size);
372 request[request_buffer_size - 1] = 0;
373 return true;
374 }
375
PingFinancialServer(Product product,const char * request,char * response,size_t response_buffer_size)376 bool PingFinancialServer(Product product, const char* request, char* response,
377 size_t response_buffer_size) {
378 if (!response || response_buffer_size == 0)
379 return false;
380 response[0] = 0;
381
382 // Check if the time is right to ping.
383 if (!FinancialPing::IsPingTime(product, false))
384 return false;
385
386 // Send out the ping.
387 std::string response_string;
388 if (!FinancialPing::PingServer(request, &response_string))
389 return false;
390
391 if (response_string.size() >= response_buffer_size)
392 return false;
393
394 strncpy(response, response_string.c_str(), response_buffer_size);
395 response[response_buffer_size - 1] = 0;
396 return true;
397 }
398
IsPingResponseValid(const char * response,int * checksum_idx)399 bool IsPingResponseValid(const char* response, int* checksum_idx) {
400 if (!response || !response[0])
401 return false;
402
403 if (checksum_idx)
404 *checksum_idx = -1;
405
406 if (strlen(response) > kMaxPingResponseLength) {
407 ASSERT_STRING("IsPingResponseValid: response is too long to parse.");
408 return false;
409 }
410
411 // Find the checksum line.
412 std::string response_string(response);
413
414 std::string checksum_param("\ncrc32: ");
415 int calculated_crc;
416 int checksum_index = response_string.find(checksum_param);
417 if (checksum_index >= 0) {
418 // Calculate checksum of message preceeding checksum line.
419 // (+ 1 to include the \n)
420 std::string message(response_string.substr(0, checksum_index + 1));
421 if (!Crc32(message.c_str(), &calculated_crc))
422 return false;
423 } else {
424 checksum_param = "crc32: "; // Empty response case.
425 if (!StartsWithASCII(response_string, checksum_param, true))
426 return false;
427
428 checksum_index = 0;
429 if (!Crc32("", &calculated_crc))
430 return false;
431 }
432
433 // Find the checksum value on the response.
434 int checksum_end = response_string.find("\n", checksum_index + 1);
435 if (checksum_end < 0)
436 checksum_end = response_string.size();
437
438 int checksum_begin = checksum_index + checksum_param.size();
439 std::string checksum = response_string.substr(checksum_begin,
440 checksum_end - checksum_begin + 1);
441 base::TrimWhitespaceASCII(checksum, base::TRIM_ALL, &checksum);
442
443 if (checksum_idx)
444 *checksum_idx = checksum_index;
445
446 return calculated_crc == HexStringToInteger(checksum.c_str());
447 }
448
449 // Complex helpers built on top of other functions.
450
ParseFinancialPingResponse(Product product,const char * response)451 bool ParseFinancialPingResponse(Product product, const char* response) {
452 // Update the last ping time irrespective of success.
453 FinancialPing::UpdateLastPingTime(product);
454 // Parse the ping response - update RLZs, clear events.
455 return ParsePingResponse(product, response);
456 }
457
SendFinancialPing(Product product,const AccessPoint * access_points,const char * product_signature,const char * product_brand,const char * product_id,const char * product_lang,bool exclude_machine_id)458 bool SendFinancialPing(Product product, const AccessPoint* access_points,
459 const char* product_signature,
460 const char* product_brand,
461 const char* product_id, const char* product_lang,
462 bool exclude_machine_id) {
463 return SendFinancialPing(product, access_points, product_signature,
464 product_brand, product_id, product_lang,
465 exclude_machine_id, false);
466 }
467
468
SendFinancialPing(Product product,const AccessPoint * access_points,const char * product_signature,const char * product_brand,const char * product_id,const char * product_lang,bool exclude_machine_id,const bool skip_time_check)469 bool SendFinancialPing(Product product, const AccessPoint* access_points,
470 const char* product_signature,
471 const char* product_brand,
472 const char* product_id, const char* product_lang,
473 bool exclude_machine_id,
474 const bool skip_time_check) {
475 // Create the financial ping request.
476 std::string request;
477 if (!FinancialPing::FormRequest(product, access_points, product_signature,
478 product_brand, product_id, product_lang,
479 exclude_machine_id, &request))
480 return false;
481
482 // Check if the time is right to ping.
483 if (!FinancialPing::IsPingTime(product, skip_time_check))
484 return false;
485
486 // Send out the ping, update the last ping time irrespective of success.
487 FinancialPing::UpdateLastPingTime(product);
488 std::string response;
489 if (!FinancialPing::PingServer(request.c_str(), &response))
490 return false;
491
492 // Parse the ping response - update RLZs, clear events.
493 return ParsePingResponse(product, response.c_str());
494 }
495
496 // TODO: Use something like RSA to make sure the response is
497 // from a Google server.
ParsePingResponse(Product product,const char * response)498 bool ParsePingResponse(Product product, const char* response) {
499 rlz_lib::ScopedRlzValueStoreLock lock;
500 rlz_lib::RlzValueStore* store = lock.GetStore();
501 if (!store || !store->HasAccess(rlz_lib::RlzValueStore::kWriteAccess))
502 return false;
503
504 std::string response_string(response);
505 int response_length = -1;
506 if (!IsPingResponseValid(response, &response_length))
507 return false;
508
509 if (0 == response_length)
510 return true; // Empty response - no parsing.
511
512 std::string events_variable;
513 std::string stateful_events_variable;
514 base::SStringPrintf(&events_variable, "%s: ", kEventsCgiVariable);
515 base::SStringPrintf(&stateful_events_variable, "%s: ",
516 kStatefulEventsCgiVariable);
517
518 int rlz_cgi_length = strlen(kRlzCgiVariable);
519
520 // Split response lines. Expected response format is lines of the form:
521 // rlzW1: 1R1_____en__252
522 int line_end_index = -1;
523 do {
524 int line_begin = line_end_index + 1;
525 line_end_index = response_string.find("\n", line_begin);
526
527 int line_end = line_end_index;
528 if (line_end < 0)
529 line_end = response_length;
530
531 if (line_end <= line_begin)
532 continue; // Empty line.
533
534 std::string response_line;
535 response_line = response_string.substr(line_begin, line_end - line_begin);
536
537 if (StartsWithASCII(response_line, kRlzCgiVariable, true)) { // An RLZ.
538 int separator_index = -1;
539 if ((separator_index = response_line.find(": ")) < 0)
540 continue; // Not a valid key-value pair.
541
542 // Get the access point.
543 std::string point_name =
544 response_line.substr(3, separator_index - rlz_cgi_length);
545 AccessPoint point = NO_ACCESS_POINT;
546 if (!GetAccessPointFromName(point_name.c_str(), &point) ||
547 point == NO_ACCESS_POINT)
548 continue; // Not a valid access point.
549
550 // Get the new RLZ.
551 std::string rlz_value(response_line.substr(separator_index + 2));
552 base::TrimWhitespaceASCII(rlz_value, base::TRIM_LEADING, &rlz_value);
553
554 size_t rlz_length = rlz_value.find_first_of("\r\n ");
555 if (rlz_length == std::string::npos)
556 rlz_length = rlz_value.size();
557
558 if (rlz_length > kMaxRlzLength)
559 continue; // Too long.
560
561 if (IsAccessPointSupported(point))
562 SetAccessPointRlz(point, rlz_value.substr(0, rlz_length).c_str());
563 } else if (StartsWithASCII(response_line, events_variable, true)) {
564 // Clear events which server parsed.
565 std::vector<ReturnedEvent> event_array;
566 GetEventsFromResponseString(response_line, events_variable, &event_array);
567 for (size_t i = 0; i < event_array.size(); ++i) {
568 ClearProductEvent(product, event_array[i].access_point,
569 event_array[i].event_type);
570 }
571 } else if (StartsWithASCII(response_line, stateful_events_variable, true)) {
572 // Record any stateful events the server send over.
573 std::vector<ReturnedEvent> event_array;
574 GetEventsFromResponseString(response_line, stateful_events_variable,
575 &event_array);
576 for (size_t i = 0; i < event_array.size(); ++i) {
577 RecordStatefulEvent(product, event_array[i].access_point,
578 event_array[i].event_type);
579 }
580 }
581 } while (line_end_index >= 0);
582
583 #if defined(OS_WIN)
584 // Update the DCC in registry if needed.
585 SetMachineDealCodeFromPingResponse(response);
586 #endif
587
588 return true;
589 }
590
GetPingParams(Product product,const AccessPoint * access_points,char * cgi,size_t cgi_size)591 bool GetPingParams(Product product, const AccessPoint* access_points,
592 char* cgi, size_t cgi_size) {
593 if (!cgi || cgi_size <= 0) {
594 ASSERT_STRING("GetPingParams: Invalid buffer");
595 return false;
596 }
597
598 cgi[0] = 0;
599
600 if (!access_points) {
601 ASSERT_STRING("GetPingParams: access_points is NULL");
602 return false;
603 }
604
605 // Add the RLZ Exchange Protocol version.
606 std::string cgi_string(kProtocolCgiArgument);
607
608 // Copy the &rlz= over.
609 base::StringAppendF(&cgi_string, "&%s=", kRlzCgiVariable);
610
611 {
612 // Now add each of the RLZ's. Keep the lock during all GetAccessPointRlz()
613 // calls below.
614 ScopedRlzValueStoreLock lock;
615 RlzValueStore* store = lock.GetStore();
616 if (!store || !store->HasAccess(RlzValueStore::kReadAccess))
617 return false;
618 bool first_rlz = true; // comma before every RLZ but the first.
619 for (int i = 0; access_points[i] != NO_ACCESS_POINT; i++) {
620 char rlz[kMaxRlzLength + 1];
621 if (GetAccessPointRlz(access_points[i], rlz, arraysize(rlz))) {
622 const char* access_point = GetAccessPointName(access_points[i]);
623 if (!access_point)
624 continue;
625
626 base::StringAppendF(&cgi_string, "%s%s%s%s",
627 first_rlz ? "" : kRlzCgiSeparator,
628 access_point, kRlzCgiIndicator, rlz);
629 first_rlz = false;
630 }
631 }
632
633 #if defined(OS_WIN)
634 // Report the DCC too if not empty. DCCs are windows-only.
635 char dcc[kMaxDccLength + 1];
636 dcc[0] = 0;
637 if (GetMachineDealCode(dcc, arraysize(dcc)) && dcc[0])
638 base::StringAppendF(&cgi_string, "&%s=%s", kDccCgiVariable, dcc);
639 #endif
640 }
641
642 if (cgi_string.size() >= cgi_size)
643 return false;
644
645 strncpy(cgi, cgi_string.c_str(), cgi_size);
646 cgi[cgi_size - 1] = 0;
647
648 return true;
649 }
650
651 } // namespace rlz_lib
652