• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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