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.
5 // A library to manage RLZ information for access-points shared
6 // across different client applications.
8 #include "rlz/lib/rlz_lib.h"
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"
21 // Event information returned from ping response.
22 struct ReturnedEvent
{
23 rlz_lib::AccessPoint access_point
;
24 rlz_lib::Event event_type
;
29 bool IsAccessPointSupported(rlz_lib::AccessPoint point
) {
31 case rlz_lib::NO_ACCESS_POINT
:
32 case rlz_lib::LAST_ACCESS_POINT
:
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.
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.
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
60 bool IsGoodRlzChar(const char ch
) {
61 if (IsAsciiAlpha(ch
) || IsAsciiDigit(ch
))
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.
86 void NormalizeRlz(const char* raw_rlz
, char* normalized_rlz
) {
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
;
93 normalized_rlz
[index
] = '.';
97 normalized_rlz
[index
] = 0;
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
);
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
);
113 // Break this up into individual events
114 int event_end_index
= -1;
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
;
120 event_end
= events_length
;
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)
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
) {
134 if (!GetEventFromName(event_string
.substr(event_string
.size() - 1).c_str(),
135 &event
) || event
== rlz_lib::INVALID_EVENT
) {
139 ReturnedEvent current_event
= {point
, event
};
140 event_array
->push_back(current_event
);
141 } while (event_end_index
>= 0);
144 // Event storage functions.
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
))
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
)
158 if (!point_name
[0] || !event_name
[0])
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());
166 bool GetProductEventsAsCgiHelper(rlz_lib::Product product
, char* cgi
,
168 rlz_lib::RlzValueStore
* store
) {
169 // Prepend the CGI param key to the buffer.
171 base::StringAppendF(&cgi_arg
, "%s=", rlz_lib::kEventsCgiVariable
);
172 if (cgi_size
<= cgi_arg
.size())
176 for (index
= 0; index
< cgi_arg
.size(); ++index
)
177 cgi
[index
] = cgi_arg
[index
];
179 // Read stored events.
180 std::vector
<std::string
> events
;
181 if (!store
->ReadProductEvents(product
, &events
))
184 // Append the events to the buffer.
185 size_t num_values
= 0;
187 for (num_values
= 0; num_values
< events
.size(); ++num_values
) {
190 int divider
= num_values
> 0 ? 1 : 0;
191 int size
= cgi_size
- (index
+ divider
);
193 return cgi_size
>= (rlz_lib::kMaxCgiLength
+ 1);
195 strncpy(cgi
+ index
+ divider
, events
[num_values
].c_str(), size
);
197 cgi
[index
] = rlz_lib::kEventsCgiSeparator
;
199 index
+= std::min((int)events
[num_values
].length(), size
) + divider
;
204 return num_values
> 0;
211 #if defined(RLZ_NETWORK_IMPLEMENTATION_CHROME_NET)
212 bool SetURLRequestContext(net::URLRequestContextGetter
* context
) {
213 return FinancialPing::SetURLRequestContext(context
);
217 bool GetProductEventsAsCgi(Product product
, char* cgi
, size_t cgi_size
) {
218 if (!cgi
|| cgi_size
<= 0) {
219 ASSERT_STRING("GetProductEventsAsCgi: Invalid buffer");
225 ScopedRlzValueStoreLock lock
;
226 RlzValueStore
* store
= lock
.GetStore();
227 if (!store
|| !store
->HasAccess(RlzValueStore::kReadAccess
))
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
);
235 ASSERT_STRING("GetProductEventsAsCgi: Possibly insufficient buffer size");
243 bool RecordProductEvent(Product product
, AccessPoint point
, Event event
) {
244 ScopedRlzValueStoreLock lock
;
245 RlzValueStore
* store
= lock
.GetStore();
246 if (!store
|| !store
->HasAccess(RlzValueStore::kWriteAccess
))
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
)
255 if (!point_name
[0] || !event_name
[0])
258 std::string new_event_value
;
259 base::StringAppendF(&new_event_value
, "%s%s", point_name
, event_name
);
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.
268 // Write the new event to the value store.
269 return store
->AddProductEvent(product
, new_event_value
.c_str());
272 bool ClearProductEvent(Product product
, AccessPoint point
, Event event
) {
273 ScopedRlzValueStoreLock lock
;
274 RlzValueStore
* store
= lock
.GetStore();
275 if (!store
|| !store
->HasAccess(RlzValueStore::kWriteAccess
))
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
)
284 if (!point_name
[0] || !event_name
[0])
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());
292 // RLZ storage functions.
294 bool GetAccessPointRlz(AccessPoint point
, char* rlz
, size_t rlz_size
) {
295 if (!rlz
|| rlz_size
<= 0) {
296 ASSERT_STRING("GetAccessPointRlz: Invalid buffer");
302 ScopedRlzValueStoreLock lock
;
303 RlzValueStore
* store
= lock
.GetStore();
304 if (!store
|| !store
->HasAccess(RlzValueStore::kReadAccess
))
307 if (!IsAccessPointSupported(point
))
310 return store
->ReadAccessPointRlz(point
, rlz
, rlz_size
);
313 bool SetAccessPointRlz(AccessPoint point
, const char* new_rlz
) {
314 ScopedRlzValueStoreLock lock
;
315 RlzValueStore
* store
= lock
.GetStore();
316 if (!store
|| !store
->HasAccess(RlzValueStore::kWriteAccess
))
320 ASSERT_STRING("SetAccessPointRlz: Invalid buffer");
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."));
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.");
338 char normalized_rlz
[kMaxRlzLength
+ 1];
339 NormalizeRlz(new_rlz
, normalized_rlz
);
340 VERIFY(strlen(new_rlz
) == rlz_length
);
342 // Setting RLZ to empty == clearing.
343 if (normalized_rlz
[0] == 0)
344 return store
->ClearAccessPointRlz(point
);
345 return store
->WriteAccessPointRlz(point
, normalized_rlz
);
348 // Financial Server pinging functions.
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)
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
))
368 if (request_string
.size() >= request_buffer_size
)
371 strncpy(request
, request_string
.c_str(), request_buffer_size
);
372 request
[request_buffer_size
- 1] = 0;
376 bool PingFinancialServer(Product product
, const char* request
, char* response
,
377 size_t response_buffer_size
) {
378 if (!response
|| response_buffer_size
== 0)
382 // Check if the time is right to ping.
383 if (!FinancialPing::IsPingTime(product
, false))
386 // Send out the ping.
387 std::string response_string
;
388 if (!FinancialPing::PingServer(request
, &response_string
))
391 if (response_string
.size() >= response_buffer_size
)
394 strncpy(response
, response_string
.c_str(), response_buffer_size
);
395 response
[response_buffer_size
- 1] = 0;
399 bool IsPingResponseValid(const char* response
, int* checksum_idx
) {
400 if (!response
|| !response
[0])
406 if (strlen(response
) > kMaxPingResponseLength
) {
407 ASSERT_STRING("IsPingResponseValid: response is too long to parse.");
411 // Find the checksum line.
412 std::string
response_string(response
);
414 std::string
checksum_param("\ncrc32: ");
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
))
424 checksum_param
= "crc32: "; // Empty response case.
425 if (!StartsWithASCII(response_string
, checksum_param
, true))
429 if (!Crc32("", &calculated_crc
))
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();
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
);
444 *checksum_idx
= checksum_index
;
446 return calculated_crc
== HexStringToInteger(checksum
.c_str());
449 // Complex helpers built on top of other functions.
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
);
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);
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.
477 if (!FinancialPing::FormRequest(product
, access_points
, product_signature
,
478 product_brand
, product_id
, product_lang
,
479 exclude_machine_id
, &request
))
482 // Check if the time is right to ping.
483 if (!FinancialPing::IsPingTime(product
, skip_time_check
))
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
))
492 // Parse the ping response - update RLZs, clear events.
493 return ParsePingResponse(product
, response
.c_str());
496 // TODO: Use something like RSA to make sure the response is
497 // from a Google server.
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
))
504 std::string
response_string(response
);
505 int response_length
= -1;
506 if (!IsPingResponseValid(response
, &response_length
))
509 if (0 == response_length
)
510 return true; // Empty response - no parsing.
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
);
518 int rlz_cgi_length
= strlen(kRlzCgiVariable
);
520 // Split response lines. Expected response format is lines of the form:
521 // rlzW1: 1R1_____en__252
522 int line_end_index
= -1;
524 int line_begin
= line_end_index
+ 1;
525 line_end_index
= response_string
.find("\n", line_begin
);
527 int line_end
= line_end_index
;
529 line_end
= response_length
;
531 if (line_end
<= line_begin
)
532 continue; // Empty line.
534 std::string response_line
;
535 response_line
= response_string
.substr(line_begin
, line_end
- line_begin
);
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.
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.
551 std::string
rlz_value(response_line
.substr(separator_index
+ 2));
552 base::TrimWhitespaceASCII(rlz_value
, base::TRIM_LEADING
, &rlz_value
);
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();
558 if (rlz_length
> kMaxRlzLength
)
559 continue; // Too long.
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
);
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
,
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
);
581 } while (line_end_index
>= 0);
584 // Update the DCC in registry if needed.
585 SetMachineDealCodeFromPingResponse(response
);
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");
600 if (!access_points
) {
601 ASSERT_STRING("GetPingParams: access_points is NULL");
605 // Add the RLZ Exchange Protocol version.
606 std::string
cgi_string(kProtocolCgiArgument
);
608 // Copy the &rlz= over.
609 base::StringAppendF(&cgi_string
, "&%s=", kRlzCgiVariable
);
612 // Now add each of the RLZ's. Keep the lock during all GetAccessPointRlz()
614 ScopedRlzValueStoreLock lock
;
615 RlzValueStore
* store
= lock
.GetStore();
616 if (!store
|| !store
->HasAccess(RlzValueStore::kReadAccess
))
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
]);
626 base::StringAppendF(&cgi_string
, "%s%s%s%s",
627 first_rlz
? "" : kRlzCgiSeparator
,
628 access_point
, kRlzCgiIndicator
, rlz
);
634 // Report the DCC too if not empty. DCCs are windows-only.
635 char dcc
[kMaxDccLength
+ 1];
637 if (GetMachineDealCode(dcc
, arraysize(dcc
)) && dcc
[0])
638 base::StringAppendF(&cgi_string
, "&%s=%s", kDccCgiVariable
, dcc
);
642 if (cgi_string
.size() >= cgi_size
)
645 strncpy(cgi
, cgi_string
.c_str(), cgi_size
);
646 cgi
[cgi_size
- 1] = 0;
651 } // namespace rlz_lib