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#include "extensions/browser/api/web_request/web_request_api_helpers.h"
6
7#include <algorithm>
8#include <cmath>
9
10#include "base/bind.h"
11#include "base/macros.h"
12#include "base/strings/string_number_conversions.h"
13#include "base/strings/string_util.h"
14#include "base/strings/stringprintf.h"
15#include "base/time/time.h"
16#include "base/values.h"
17#include "components/web_cache/browser/web_cache_manager.h"
18#include "content/public/browser/browser_thread.h"
19#include "content/public/browser/render_process_host.h"
20#include "extensions/browser/api/web_request/web_request_api_constants.h"
21#include "extensions/browser/extension_registry.h"
22#include "extensions/browser/extension_system.h"
23#include "extensions/browser/extensions_browser_client.h"
24#include "extensions/browser/runtime_data.h"
25#include "extensions/browser/warning_set.h"
26#include "extensions/common/extension_messages.h"
27#include "net/base/net_log.h"
28#include "net/cookies/cookie_util.h"
29#include "net/cookies/parsed_cookie.h"
30#include "net/http/http_util.h"
31#include "net/url_request/url_request.h"
32#include "url/url_constants.h"
33
34// TODO(battre): move all static functions into an anonymous namespace at the
35// top of this file.
36
37using base::Time;
38using content::ResourceType;
39using net::cookie_util::ParsedRequestCookie;
40using net::cookie_util::ParsedRequestCookies;
41
42namespace keys = extension_web_request_api_constants;
43
44namespace extension_web_request_api_helpers {
45
46namespace {
47
48static const char* kResourceTypeStrings[] = {
49  "main_frame",
50  "sub_frame",
51  "stylesheet",
52  "script",
53  "image",
54  "object",
55  "xmlhttprequest",
56  "other",
57  "other",
58};
59
60const size_t kResourceTypeStringsLength = arraysize(kResourceTypeStrings);
61
62static ResourceType kResourceTypeValues[] = {
63  content::RESOURCE_TYPE_MAIN_FRAME,
64  content::RESOURCE_TYPE_SUB_FRAME,
65  content::RESOURCE_TYPE_STYLESHEET,
66  content::RESOURCE_TYPE_SCRIPT,
67  content::RESOURCE_TYPE_IMAGE,
68  content::RESOURCE_TYPE_OBJECT,
69  content::RESOURCE_TYPE_XHR,
70  content::RESOURCE_TYPE_LAST_TYPE,  // represents "other"
71  // TODO(jochen): We duplicate the last entry, so the array's size is not a
72  // power of two. If it is, this triggers a bug in gcc 4.4 in Release builds
73  // (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=43949). Once we use a version
74  // of gcc with this bug fixed, or the array is changed so this duplicate
75  // entry is no longer required, this should be removed.
76  content::RESOURCE_TYPE_LAST_TYPE,
77};
78
79const size_t kResourceTypeValuesLength = arraysize(kResourceTypeValues);
80
81typedef std::vector<linked_ptr<net::ParsedCookie> > ParsedResponseCookies;
82
83void ClearCacheOnNavigationOnUI() {
84  web_cache::WebCacheManager::GetInstance()->ClearCacheOnNavigation();
85}
86
87bool ParseCookieLifetime(net::ParsedCookie* cookie,
88                         int64* seconds_till_expiry) {
89  // 'Max-Age' is processed first because according to:
90  // http://tools.ietf.org/html/rfc6265#section-5.3 'Max-Age' attribute
91  // overrides 'Expires' attribute.
92  if (cookie->HasMaxAge() &&
93      base::StringToInt64(cookie->MaxAge(), seconds_till_expiry)) {
94    return true;
95  }
96
97  Time parsed_expiry_time;
98  if (cookie->HasExpires())
99    parsed_expiry_time = net::cookie_util::ParseCookieTime(cookie->Expires());
100
101  if (!parsed_expiry_time.is_null()) {
102    *seconds_till_expiry =
103        ceil((parsed_expiry_time - Time::Now()).InSecondsF());
104    return *seconds_till_expiry >= 0;
105  }
106  return false;
107}
108
109bool NullableEquals(const int* a, const int* b) {
110  if ((a && !b) || (!a && b))
111    return false;
112  return (!a) || (*a == *b);
113}
114
115bool NullableEquals(const bool* a, const bool* b) {
116  if ((a && !b) || (!a && b))
117    return false;
118  return (!a) || (*a == *b);
119}
120
121bool NullableEquals(const std::string* a, const std::string* b) {
122  if ((a && !b) || (!a && b))
123    return false;
124  return (!a) || (*a == *b);
125}
126
127}  // namespace
128
129RequestCookie::RequestCookie() {}
130RequestCookie::~RequestCookie() {}
131
132bool NullableEquals(const RequestCookie* a, const RequestCookie* b) {
133  if ((a && !b) || (!a && b))
134    return false;
135  if (!a)
136    return true;
137  return NullableEquals(a->name.get(), b->name.get()) &&
138         NullableEquals(a->value.get(), b->value.get());
139}
140
141ResponseCookie::ResponseCookie() {}
142ResponseCookie::~ResponseCookie() {}
143
144bool NullableEquals(const ResponseCookie* a, const ResponseCookie* b) {
145  if ((a && !b) || (!a && b))
146    return false;
147  if (!a)
148    return true;
149  return NullableEquals(a->name.get(), b->name.get()) &&
150         NullableEquals(a->value.get(), b->value.get()) &&
151         NullableEquals(a->expires.get(), b->expires.get()) &&
152         NullableEquals(a->max_age.get(), b->max_age.get()) &&
153         NullableEquals(a->domain.get(), b->domain.get()) &&
154         NullableEquals(a->path.get(), b->path.get()) &&
155         NullableEquals(a->secure.get(), b->secure.get()) &&
156         NullableEquals(a->http_only.get(), b->http_only.get());
157}
158
159FilterResponseCookie::FilterResponseCookie() {}
160FilterResponseCookie::~FilterResponseCookie() {}
161
162bool NullableEquals(const FilterResponseCookie* a,
163                    const FilterResponseCookie* b) {
164  if ((a && !b) || (!a && b))
165    return false;
166  if (!a)
167    return true;
168  return NullableEquals(a->age_lower_bound.get(), b->age_lower_bound.get()) &&
169         NullableEquals(a->age_upper_bound.get(), b->age_upper_bound.get()) &&
170         NullableEquals(a->session_cookie.get(), b->session_cookie.get());
171}
172
173RequestCookieModification::RequestCookieModification() {}
174RequestCookieModification::~RequestCookieModification() {}
175
176bool NullableEquals(const RequestCookieModification* a,
177                    const RequestCookieModification* b) {
178  if ((a && !b) || (!a && b))
179    return false;
180  if (!a)
181    return true;
182  return NullableEquals(a->filter.get(), b->filter.get()) &&
183         NullableEquals(a->modification.get(), b->modification.get());
184}
185
186ResponseCookieModification::ResponseCookieModification() : type(ADD) {}
187ResponseCookieModification::~ResponseCookieModification() {}
188
189bool NullableEquals(const ResponseCookieModification* a,
190                    const ResponseCookieModification* b) {
191  if ((a && !b) || (!a && b))
192    return false;
193  if (!a)
194    return true;
195  return a->type == b->type &&
196         NullableEquals(a->filter.get(), b->filter.get()) &&
197         NullableEquals(a->modification.get(), b->modification.get());
198}
199
200EventResponseDelta::EventResponseDelta(
201    const std::string& extension_id, const base::Time& extension_install_time)
202    : extension_id(extension_id),
203      extension_install_time(extension_install_time),
204      cancel(false) {
205}
206
207EventResponseDelta::~EventResponseDelta() {
208}
209
210
211// Creates a NetLog callback the returns a Value with the ID of the extension
212// that caused an event.  |delta| must remain valid for the lifetime of the
213// callback.
214net::NetLog::ParametersCallback CreateNetLogExtensionIdCallback(
215    const EventResponseDelta* delta) {
216  return net::NetLog::StringCallback("extension_id", &delta->extension_id);
217}
218
219// Creates NetLog parameters to indicate that an extension modified a request.
220// Caller takes ownership of returned value.
221base::Value* NetLogModificationCallback(
222    const EventResponseDelta* delta,
223    net::NetLog::LogLevel log_level) {
224  base::DictionaryValue* dict = new base::DictionaryValue();
225  dict->SetString("extension_id", delta->extension_id);
226
227  base::ListValue* modified_headers = new base::ListValue();
228  net::HttpRequestHeaders::Iterator modification(
229      delta->modified_request_headers);
230  while (modification.GetNext()) {
231    std::string line = modification.name() + ": " + modification.value();
232    modified_headers->Append(new base::StringValue(line));
233  }
234  dict->Set("modified_headers", modified_headers);
235
236  base::ListValue* deleted_headers = new base::ListValue();
237  for (std::vector<std::string>::const_iterator key =
238           delta->deleted_request_headers.begin();
239       key != delta->deleted_request_headers.end();
240       ++key) {
241    deleted_headers->Append(new base::StringValue(*key));
242  }
243  dict->Set("deleted_headers", deleted_headers);
244  return dict;
245}
246
247bool InDecreasingExtensionInstallationTimeOrder(
248    const linked_ptr<EventResponseDelta>& a,
249    const linked_ptr<EventResponseDelta>& b) {
250  return a->extension_install_time > b->extension_install_time;
251}
252
253base::ListValue* StringToCharList(const std::string& s) {
254  base::ListValue* result = new base::ListValue;
255  for (size_t i = 0, n = s.size(); i < n; ++i) {
256    result->Append(
257        new base::FundamentalValue(
258            *reinterpret_cast<const unsigned char*>(&s[i])));
259  }
260  return result;
261}
262
263bool CharListToString(const base::ListValue* list, std::string* out) {
264  if (!list)
265    return false;
266  const size_t list_length = list->GetSize();
267  out->resize(list_length);
268  int value = 0;
269  for (size_t i = 0; i < list_length; ++i) {
270    if (!list->GetInteger(i, &value) || value < 0 || value > 255)
271      return false;
272    unsigned char tmp = static_cast<unsigned char>(value);
273    (*out)[i] = *reinterpret_cast<char*>(&tmp);
274  }
275  return true;
276}
277
278EventResponseDelta* CalculateOnBeforeRequestDelta(
279    const std::string& extension_id,
280    const base::Time& extension_install_time,
281    bool cancel,
282    const GURL& new_url) {
283  EventResponseDelta* result =
284      new EventResponseDelta(extension_id, extension_install_time);
285  result->cancel = cancel;
286  result->new_url = new_url;
287  return result;
288}
289
290EventResponseDelta* CalculateOnBeforeSendHeadersDelta(
291    const std::string& extension_id,
292    const base::Time& extension_install_time,
293    bool cancel,
294    net::HttpRequestHeaders* old_headers,
295    net::HttpRequestHeaders* new_headers) {
296  EventResponseDelta* result =
297      new EventResponseDelta(extension_id, extension_install_time);
298  result->cancel = cancel;
299
300  // The event listener might not have passed any new headers if he
301  // just wanted to cancel the request.
302  if (new_headers) {
303    // Find deleted headers.
304    {
305      net::HttpRequestHeaders::Iterator i(*old_headers);
306      while (i.GetNext()) {
307        if (!new_headers->HasHeader(i.name())) {
308          result->deleted_request_headers.push_back(i.name());
309        }
310      }
311    }
312
313    // Find modified headers.
314    {
315      net::HttpRequestHeaders::Iterator i(*new_headers);
316      while (i.GetNext()) {
317        std::string value;
318        if (!old_headers->GetHeader(i.name(), &value) || i.value() != value) {
319          result->modified_request_headers.SetHeader(i.name(), i.value());
320        }
321      }
322    }
323  }
324  return result;
325}
326
327EventResponseDelta* CalculateOnHeadersReceivedDelta(
328    const std::string& extension_id,
329    const base::Time& extension_install_time,
330    bool cancel,
331    const GURL& new_url,
332    const net::HttpResponseHeaders* old_response_headers,
333    ResponseHeaders* new_response_headers) {
334  EventResponseDelta* result =
335      new EventResponseDelta(extension_id, extension_install_time);
336  result->cancel = cancel;
337  result->new_url = new_url;
338
339  if (!new_response_headers)
340    return result;
341
342  // Find deleted headers (header keys are treated case insensitively).
343  {
344    void* iter = NULL;
345    std::string name;
346    std::string value;
347    while (old_response_headers->EnumerateHeaderLines(&iter, &name, &value)) {
348      std::string name_lowercase(name);
349      base::StringToLowerASCII(&name_lowercase);
350
351      bool header_found = false;
352      for (ResponseHeaders::const_iterator i = new_response_headers->begin();
353           i != new_response_headers->end(); ++i) {
354        if (LowerCaseEqualsASCII(i->first, name_lowercase.c_str()) &&
355            value == i->second) {
356          header_found = true;
357          break;
358        }
359      }
360      if (!header_found)
361        result->deleted_response_headers.push_back(ResponseHeader(name, value));
362    }
363  }
364
365  // Find added headers (header keys are treated case insensitively).
366  {
367    for (ResponseHeaders::const_iterator i = new_response_headers->begin();
368         i != new_response_headers->end(); ++i) {
369      void* iter = NULL;
370      std::string value;
371      bool header_found = false;
372      while (old_response_headers->EnumerateHeader(&iter, i->first, &value) &&
373             !header_found) {
374        header_found = (value == i->second);
375      }
376      if (!header_found)
377        result->added_response_headers.push_back(*i);
378    }
379  }
380
381  return result;
382}
383
384EventResponseDelta* CalculateOnAuthRequiredDelta(
385    const std::string& extension_id,
386    const base::Time& extension_install_time,
387    bool cancel,
388    scoped_ptr<net::AuthCredentials>* auth_credentials) {
389  EventResponseDelta* result =
390      new EventResponseDelta(extension_id, extension_install_time);
391  result->cancel = cancel;
392  result->auth_credentials.swap(*auth_credentials);
393  return result;
394}
395
396void MergeCancelOfResponses(
397    const EventResponseDeltas& deltas,
398    bool* canceled,
399    const net::BoundNetLog* net_log) {
400  for (EventResponseDeltas::const_iterator i = deltas.begin();
401       i != deltas.end(); ++i) {
402    if ((*i)->cancel) {
403      *canceled = true;
404      net_log->AddEvent(
405          net::NetLog::TYPE_CHROME_EXTENSION_ABORTED_REQUEST,
406          CreateNetLogExtensionIdCallback(i->get()));
407      break;
408    }
409  }
410}
411
412// Helper function for MergeRedirectUrlOfResponses() that allows ignoring
413// all redirects but those to data:// urls and about:blank. This is important
414// to treat these URLs as "cancel urls", i.e. URLs that extensions redirect
415// to if they want to express that they want to cancel a request. This reduces
416// the number of conflicts that we need to flag, as canceling is considered
417// a higher precedence operation that redirects.
418// Returns whether a redirect occurred.
419static bool MergeRedirectUrlOfResponsesHelper(
420    const EventResponseDeltas& deltas,
421    GURL* new_url,
422    extensions::WarningSet* conflicting_extensions,
423    const net::BoundNetLog* net_log,
424    bool consider_only_cancel_scheme_urls) {
425  bool redirected = false;
426
427  // Extension that determines the |new_url|.
428  std::string winning_extension_id;
429  EventResponseDeltas::const_iterator delta;
430  for (delta = deltas.begin(); delta != deltas.end(); ++delta) {
431    if ((*delta)->new_url.is_empty())
432      continue;
433    if (consider_only_cancel_scheme_urls &&
434        !(*delta)->new_url.SchemeIs(url::kDataScheme) &&
435        (*delta)->new_url.spec() != "about:blank") {
436      continue;
437    }
438
439    if (!redirected || *new_url == (*delta)->new_url) {
440      *new_url = (*delta)->new_url;
441      winning_extension_id = (*delta)->extension_id;
442      redirected = true;
443      net_log->AddEvent(
444          net::NetLog::TYPE_CHROME_EXTENSION_REDIRECTED_REQUEST,
445          CreateNetLogExtensionIdCallback(delta->get()));
446    } else {
447      conflicting_extensions->insert(
448          extensions::Warning::CreateRedirectConflictWarning(
449              (*delta)->extension_id,
450              winning_extension_id,
451              (*delta)->new_url,
452              *new_url));
453      net_log->AddEvent(
454          net::NetLog::TYPE_CHROME_EXTENSION_IGNORED_DUE_TO_CONFLICT,
455          CreateNetLogExtensionIdCallback(delta->get()));
456    }
457  }
458  return redirected;
459}
460
461void MergeRedirectUrlOfResponses(
462    const EventResponseDeltas& deltas,
463    GURL* new_url,
464    extensions::WarningSet* conflicting_extensions,
465    const net::BoundNetLog* net_log) {
466
467  // First handle only redirects to data:// URLs and about:blank. These are a
468  // special case as they represent a way of cancelling a request.
469  if (MergeRedirectUrlOfResponsesHelper(
470          deltas, new_url, conflicting_extensions, net_log, true)) {
471    // If any extension cancelled a request by redirecting to a data:// URL or
472    // about:blank, we don't consider the other redirects.
473    return;
474  }
475
476  // Handle all other redirects.
477  MergeRedirectUrlOfResponsesHelper(
478      deltas, new_url, conflicting_extensions, net_log, false);
479}
480
481void MergeOnBeforeRequestResponses(
482    const EventResponseDeltas& deltas,
483    GURL* new_url,
484    extensions::WarningSet* conflicting_extensions,
485    const net::BoundNetLog* net_log) {
486  MergeRedirectUrlOfResponses(deltas, new_url, conflicting_extensions, net_log);
487}
488
489static bool DoesRequestCookieMatchFilter(
490    const ParsedRequestCookie& cookie,
491    RequestCookie* filter) {
492  if (!filter) return true;
493  if (filter->name.get() && cookie.first != *filter->name) return false;
494  if (filter->value.get() && cookie.second != *filter->value) return false;
495  return true;
496}
497
498// Applies all CookieModificationType::ADD operations for request cookies of
499// |deltas| to |cookies|. Returns whether any cookie was added.
500static bool MergeAddRequestCookieModifications(
501    const EventResponseDeltas& deltas,
502    ParsedRequestCookies* cookies) {
503  bool modified = false;
504  // We assume here that the deltas are sorted in decreasing extension
505  // precedence (i.e. decreasing extension installation time).
506  EventResponseDeltas::const_reverse_iterator delta;
507  for (delta = deltas.rbegin(); delta != deltas.rend(); ++delta) {
508    const RequestCookieModifications& modifications =
509        (*delta)->request_cookie_modifications;
510    for (RequestCookieModifications::const_iterator mod = modifications.begin();
511         mod != modifications.end(); ++mod) {
512      if ((*mod)->type != ADD || !(*mod)->modification.get())
513        continue;
514      std::string* new_name = (*mod)->modification->name.get();
515      std::string* new_value = (*mod)->modification->value.get();
516      if (!new_name || !new_value)
517        continue;
518
519      bool cookie_with_same_name_found = false;
520      for (ParsedRequestCookies::iterator cookie = cookies->begin();
521           cookie != cookies->end() && !cookie_with_same_name_found; ++cookie) {
522        if (cookie->first == *new_name) {
523          if (cookie->second != *new_value) {
524            cookie->second = *new_value;
525            modified = true;
526          }
527          cookie_with_same_name_found = true;
528        }
529      }
530      if (!cookie_with_same_name_found) {
531        cookies->push_back(std::make_pair(base::StringPiece(*new_name),
532                                          base::StringPiece(*new_value)));
533        modified = true;
534      }
535    }
536  }
537  return modified;
538}
539
540// Applies all CookieModificationType::EDIT operations for request cookies of
541// |deltas| to |cookies|. Returns whether any cookie was modified.
542static bool MergeEditRequestCookieModifications(
543    const EventResponseDeltas& deltas,
544    ParsedRequestCookies* cookies) {
545  bool modified = false;
546  // We assume here that the deltas are sorted in decreasing extension
547  // precedence (i.e. decreasing extension installation time).
548  EventResponseDeltas::const_reverse_iterator delta;
549  for (delta = deltas.rbegin(); delta != deltas.rend(); ++delta) {
550    const RequestCookieModifications& modifications =
551        (*delta)->request_cookie_modifications;
552    for (RequestCookieModifications::const_iterator mod = modifications.begin();
553         mod != modifications.end(); ++mod) {
554      if ((*mod)->type != EDIT || !(*mod)->modification.get())
555        continue;
556
557      std::string* new_value = (*mod)->modification->value.get();
558      RequestCookie* filter = (*mod)->filter.get();
559      for (ParsedRequestCookies::iterator cookie = cookies->begin();
560           cookie != cookies->end(); ++cookie) {
561        if (!DoesRequestCookieMatchFilter(*cookie, filter))
562          continue;
563        // If the edit operation tries to modify the cookie name, we just ignore
564        // this. We only modify the cookie value.
565        if (new_value && cookie->second != *new_value) {
566          cookie->second = *new_value;
567          modified = true;
568        }
569      }
570    }
571  }
572  return modified;
573}
574
575// Applies all CookieModificationType::REMOVE operations for request cookies of
576// |deltas| to |cookies|. Returns whether any cookie was deleted.
577static bool MergeRemoveRequestCookieModifications(
578    const EventResponseDeltas& deltas,
579    ParsedRequestCookies* cookies) {
580  bool modified = false;
581  // We assume here that the deltas are sorted in decreasing extension
582  // precedence (i.e. decreasing extension installation time).
583  EventResponseDeltas::const_reverse_iterator delta;
584  for (delta = deltas.rbegin(); delta != deltas.rend(); ++delta) {
585    const RequestCookieModifications& modifications =
586        (*delta)->request_cookie_modifications;
587    for (RequestCookieModifications::const_iterator mod = modifications.begin();
588         mod != modifications.end(); ++mod) {
589      if ((*mod)->type != REMOVE)
590        continue;
591
592      RequestCookie* filter = (*mod)->filter.get();
593      ParsedRequestCookies::iterator i = cookies->begin();
594      while (i != cookies->end()) {
595        if (DoesRequestCookieMatchFilter(*i, filter)) {
596          i = cookies->erase(i);
597          modified = true;
598        } else {
599          ++i;
600        }
601      }
602    }
603  }
604  return modified;
605}
606
607void MergeCookiesInOnBeforeSendHeadersResponses(
608    const EventResponseDeltas& deltas,
609    net::HttpRequestHeaders* request_headers,
610    extensions::WarningSet* conflicting_extensions,
611    const net::BoundNetLog* net_log) {
612  // Skip all work if there are no registered cookie modifications.
613  bool cookie_modifications_exist = false;
614  EventResponseDeltas::const_iterator delta;
615  for (delta = deltas.begin(); delta != deltas.end(); ++delta) {
616    cookie_modifications_exist |=
617        !(*delta)->request_cookie_modifications.empty();
618  }
619  if (!cookie_modifications_exist)
620    return;
621
622  // Parse old cookie line.
623  std::string cookie_header;
624  request_headers->GetHeader(net::HttpRequestHeaders::kCookie, &cookie_header);
625  ParsedRequestCookies cookies;
626  net::cookie_util::ParseRequestCookieLine(cookie_header, &cookies);
627
628  // Modify cookies.
629  bool modified = false;
630  modified |= MergeAddRequestCookieModifications(deltas, &cookies);
631  modified |= MergeEditRequestCookieModifications(deltas, &cookies);
632  modified |= MergeRemoveRequestCookieModifications(deltas, &cookies);
633
634  // Reassemble and store new cookie line.
635  if (modified) {
636    std::string new_cookie_header =
637        net::cookie_util::SerializeRequestCookieLine(cookies);
638    request_headers->SetHeader(net::HttpRequestHeaders::kCookie,
639                               new_cookie_header);
640  }
641}
642
643// Returns the extension ID of the first extension in |deltas| that sets the
644// request header identified by |key| to |value|.
645static std::string FindSetRequestHeader(
646    const EventResponseDeltas& deltas,
647    const std::string& key,
648    const std::string& value) {
649  EventResponseDeltas::const_iterator delta;
650  for (delta = deltas.begin(); delta != deltas.end(); ++delta) {
651    net::HttpRequestHeaders::Iterator modification(
652        (*delta)->modified_request_headers);
653    while (modification.GetNext()) {
654      if (key == modification.name() && value == modification.value())
655        return (*delta)->extension_id;
656    }
657  }
658  return std::string();
659}
660
661// Returns the extension ID of the first extension in |deltas| that removes the
662// request header identified by |key|.
663static std::string FindRemoveRequestHeader(
664    const EventResponseDeltas& deltas,
665    const std::string& key) {
666  EventResponseDeltas::const_iterator delta;
667  for (delta = deltas.begin(); delta != deltas.end(); ++delta) {
668    std::vector<std::string>::iterator i;
669    for (i = (*delta)->deleted_request_headers.begin();
670         i != (*delta)->deleted_request_headers.end();
671         ++i) {
672      if (*i == key)
673        return (*delta)->extension_id;
674    }
675  }
676  return std::string();
677}
678
679void MergeOnBeforeSendHeadersResponses(
680    const EventResponseDeltas& deltas,
681    net::HttpRequestHeaders* request_headers,
682    extensions::WarningSet* conflicting_extensions,
683    const net::BoundNetLog* net_log) {
684  EventResponseDeltas::const_iterator delta;
685
686  // Here we collect which headers we have removed or set to new values
687  // so far due to extensions of higher precedence.
688  std::set<std::string> removed_headers;
689  std::set<std::string> set_headers;
690
691  // We assume here that the deltas are sorted in decreasing extension
692  // precedence (i.e. decreasing extension installation time).
693  for (delta = deltas.begin(); delta != deltas.end(); ++delta) {
694    if ((*delta)->modified_request_headers.IsEmpty() &&
695        (*delta)->deleted_request_headers.empty()) {
696      continue;
697    }
698
699    // Check whether any modification affects a request header that
700    // has been modified differently before. As deltas is sorted by decreasing
701    // extension installation order, this takes care of precedence.
702    bool extension_conflicts = false;
703    std::string winning_extension_id;
704    std::string conflicting_header;
705    {
706      net::HttpRequestHeaders::Iterator modification(
707          (*delta)->modified_request_headers);
708      while (modification.GetNext() && !extension_conflicts) {
709        // This modification sets |key| to |value|.
710        const std::string& key = modification.name();
711        const std::string& value = modification.value();
712
713        // We must not delete anything that has been modified before.
714        if (removed_headers.find(key) != removed_headers.end() &&
715            !extension_conflicts) {
716          winning_extension_id = FindRemoveRequestHeader(deltas, key);
717          conflicting_header = key;
718          extension_conflicts = true;
719        }
720
721        // We must not modify anything that has been set to a *different*
722        // value before.
723        if (set_headers.find(key) != set_headers.end() &&
724            !extension_conflicts) {
725          std::string current_value;
726          if (!request_headers->GetHeader(key, &current_value) ||
727              current_value != value) {
728            winning_extension_id =
729                FindSetRequestHeader(deltas, key, current_value);
730            conflicting_header = key;
731            extension_conflicts = true;
732          }
733        }
734      }
735    }
736
737    // Check whether any deletion affects a request header that has been
738    // modified before.
739    {
740      std::vector<std::string>::iterator key;
741      for (key = (*delta)->deleted_request_headers.begin();
742           key != (*delta)->deleted_request_headers.end() &&
743               !extension_conflicts;
744           ++key) {
745        if (set_headers.find(*key) != set_headers.end()) {
746          std::string current_value;
747          request_headers->GetHeader(*key, &current_value);
748          winning_extension_id =
749              FindSetRequestHeader(deltas, *key, current_value);
750          conflicting_header = *key;
751          extension_conflicts = true;
752        }
753      }
754    }
755
756    // Now execute the modifications if there were no conflicts.
757    if (!extension_conflicts) {
758      // Copy all modifications into the original headers.
759      request_headers->MergeFrom((*delta)->modified_request_headers);
760      {
761        // Record which keys were changed.
762        net::HttpRequestHeaders::Iterator modification(
763            (*delta)->modified_request_headers);
764        while (modification.GetNext())
765          set_headers.insert(modification.name());
766      }
767
768      // Perform all deletions and record which keys were deleted.
769      {
770        std::vector<std::string>::iterator key;
771        for (key = (*delta)->deleted_request_headers.begin();
772             key != (*delta)->deleted_request_headers.end();
773             ++key) {
774          request_headers->RemoveHeader(*key);
775          removed_headers.insert(*key);
776        }
777      }
778      net_log->AddEvent(
779          net::NetLog::TYPE_CHROME_EXTENSION_MODIFIED_HEADERS,
780          base::Bind(&NetLogModificationCallback, delta->get()));
781    } else {
782      conflicting_extensions->insert(
783          extensions::Warning::CreateRequestHeaderConflictWarning(
784              (*delta)->extension_id, winning_extension_id,
785              conflicting_header));
786      net_log->AddEvent(
787          net::NetLog::TYPE_CHROME_EXTENSION_IGNORED_DUE_TO_CONFLICT,
788          CreateNetLogExtensionIdCallback(delta->get()));
789    }
790  }
791
792  MergeCookiesInOnBeforeSendHeadersResponses(deltas, request_headers,
793      conflicting_extensions, net_log);
794}
795
796// Retrives all cookies from |override_response_headers|.
797static ParsedResponseCookies GetResponseCookies(
798    scoped_refptr<net::HttpResponseHeaders> override_response_headers) {
799  ParsedResponseCookies result;
800
801  void* iter = NULL;
802  std::string value;
803  while (override_response_headers->EnumerateHeader(&iter, "Set-Cookie",
804                                                    &value)) {
805    result.push_back(make_linked_ptr(new net::ParsedCookie(value)));
806  }
807  return result;
808}
809
810// Stores all |cookies| in |override_response_headers| deleting previously
811// existing cookie definitions.
812static void StoreResponseCookies(
813    const ParsedResponseCookies& cookies,
814    scoped_refptr<net::HttpResponseHeaders> override_response_headers) {
815  override_response_headers->RemoveHeader("Set-Cookie");
816  for (ParsedResponseCookies::const_iterator i = cookies.begin();
817       i != cookies.end(); ++i) {
818    override_response_headers->AddHeader("Set-Cookie: " + (*i)->ToCookieLine());
819  }
820}
821
822// Modifies |cookie| according to |modification|. Each value that is set in
823// |modification| is applied to |cookie|.
824static bool ApplyResponseCookieModification(ResponseCookie* modification,
825                                            net::ParsedCookie* cookie) {
826  bool modified = false;
827  if (modification->name.get())
828    modified |= cookie->SetName(*modification->name);
829  if (modification->value.get())
830    modified |= cookie->SetValue(*modification->value);
831  if (modification->expires.get())
832    modified |= cookie->SetExpires(*modification->expires);
833  if (modification->max_age.get())
834    modified |= cookie->SetMaxAge(base::IntToString(*modification->max_age));
835  if (modification->domain.get())
836    modified |= cookie->SetDomain(*modification->domain);
837  if (modification->path.get())
838    modified |= cookie->SetPath(*modification->path);
839  if (modification->secure.get())
840    modified |= cookie->SetIsSecure(*modification->secure);
841  if (modification->http_only.get())
842    modified |= cookie->SetIsHttpOnly(*modification->http_only);
843  return modified;
844}
845
846static bool DoesResponseCookieMatchFilter(net::ParsedCookie* cookie,
847                                          FilterResponseCookie* filter) {
848  if (!cookie->IsValid()) return false;
849  if (!filter) return true;
850  if (filter->name && cookie->Name() != *filter->name)
851    return false;
852  if (filter->value && cookie->Value() != *filter->value)
853    return false;
854  if (filter->expires) {
855    std::string actual_value =
856        cookie->HasExpires() ? cookie->Expires() : std::string();
857    if (actual_value != *filter->expires)
858      return false;
859  }
860  if (filter->max_age) {
861    std::string actual_value =
862        cookie->HasMaxAge() ? cookie->MaxAge() : std::string();
863    if (actual_value != base::IntToString(*filter->max_age))
864      return false;
865  }
866  if (filter->domain) {
867    std::string actual_value =
868        cookie->HasDomain() ? cookie->Domain() : std::string();
869    if (actual_value != *filter->domain)
870      return false;
871  }
872  if (filter->path) {
873    std::string actual_value =
874        cookie->HasPath() ? cookie->Path() : std::string();
875    if (actual_value != *filter->path)
876      return false;
877  }
878  if (filter->secure && cookie->IsSecure() != *filter->secure)
879    return false;
880  if (filter->http_only && cookie->IsHttpOnly() != *filter->http_only)
881    return false;
882  if (filter->age_upper_bound || filter->age_lower_bound ||
883      (filter->session_cookie && *filter->session_cookie)) {
884    int64 seconds_to_expiry;
885    bool lifetime_parsed = ParseCookieLifetime(cookie, &seconds_to_expiry);
886    if (filter->age_upper_bound && seconds_to_expiry > *filter->age_upper_bound)
887      return false;
888    if (filter->age_lower_bound && seconds_to_expiry < *filter->age_lower_bound)
889      return false;
890    if (filter->session_cookie && *filter->session_cookie && lifetime_parsed)
891      return false;
892  }
893  return true;
894}
895
896// Applies all CookieModificationType::ADD operations for response cookies of
897// |deltas| to |cookies|. Returns whether any cookie was added.
898static bool MergeAddResponseCookieModifications(
899    const EventResponseDeltas& deltas,
900    ParsedResponseCookies* cookies) {
901  bool modified = false;
902  // We assume here that the deltas are sorted in decreasing extension
903  // precedence (i.e. decreasing extension installation time).
904  EventResponseDeltas::const_reverse_iterator delta;
905  for (delta = deltas.rbegin(); delta != deltas.rend(); ++delta) {
906    const ResponseCookieModifications& modifications =
907        (*delta)->response_cookie_modifications;
908    for (ResponseCookieModifications::const_iterator mod =
909             modifications.begin(); mod != modifications.end(); ++mod) {
910      if ((*mod)->type != ADD || !(*mod)->modification.get())
911        continue;
912      // Cookie names are not unique in response cookies so we always append
913      // and never override.
914      linked_ptr<net::ParsedCookie> cookie(
915          new net::ParsedCookie(std::string()));
916      ApplyResponseCookieModification((*mod)->modification.get(), cookie.get());
917      cookies->push_back(cookie);
918      modified = true;
919    }
920  }
921  return modified;
922}
923
924// Applies all CookieModificationType::EDIT operations for response cookies of
925// |deltas| to |cookies|. Returns whether any cookie was modified.
926static bool MergeEditResponseCookieModifications(
927    const EventResponseDeltas& deltas,
928    ParsedResponseCookies* cookies) {
929  bool modified = false;
930  // We assume here that the deltas are sorted in decreasing extension
931  // precedence (i.e. decreasing extension installation time).
932  EventResponseDeltas::const_reverse_iterator delta;
933  for (delta = deltas.rbegin(); delta != deltas.rend(); ++delta) {
934    const ResponseCookieModifications& modifications =
935        (*delta)->response_cookie_modifications;
936    for (ResponseCookieModifications::const_iterator mod =
937             modifications.begin(); mod != modifications.end(); ++mod) {
938      if ((*mod)->type != EDIT || !(*mod)->modification.get())
939        continue;
940
941      for (ParsedResponseCookies::iterator cookie = cookies->begin();
942           cookie != cookies->end(); ++cookie) {
943        if (DoesResponseCookieMatchFilter(cookie->get(),
944                                          (*mod)->filter.get())) {
945          modified |= ApplyResponseCookieModification(
946              (*mod)->modification.get(), cookie->get());
947        }
948      }
949    }
950  }
951  return modified;
952}
953
954// Applies all CookieModificationType::REMOVE operations for response cookies of
955// |deltas| to |cookies|. Returns whether any cookie was deleted.
956static bool MergeRemoveResponseCookieModifications(
957    const EventResponseDeltas& deltas,
958    ParsedResponseCookies* cookies) {
959  bool modified = false;
960  // We assume here that the deltas are sorted in decreasing extension
961  // precedence (i.e. decreasing extension installation time).
962  EventResponseDeltas::const_reverse_iterator delta;
963  for (delta = deltas.rbegin(); delta != deltas.rend(); ++delta) {
964    const ResponseCookieModifications& modifications =
965        (*delta)->response_cookie_modifications;
966    for (ResponseCookieModifications::const_iterator mod =
967             modifications.begin(); mod != modifications.end(); ++mod) {
968      if ((*mod)->type != REMOVE)
969        continue;
970
971      ParsedResponseCookies::iterator i = cookies->begin();
972      while (i != cookies->end()) {
973        if (DoesResponseCookieMatchFilter(i->get(),
974                                          (*mod)->filter.get())) {
975          i = cookies->erase(i);
976          modified = true;
977        } else {
978          ++i;
979        }
980      }
981    }
982  }
983  return modified;
984}
985
986void MergeCookiesInOnHeadersReceivedResponses(
987    const EventResponseDeltas& deltas,
988    const net::HttpResponseHeaders* original_response_headers,
989    scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
990    extensions::WarningSet* conflicting_extensions,
991    const net::BoundNetLog* net_log) {
992  // Skip all work if there are no registered cookie modifications.
993  bool cookie_modifications_exist = false;
994  EventResponseDeltas::const_reverse_iterator delta;
995  for (delta = deltas.rbegin(); delta != deltas.rend(); ++delta) {
996    cookie_modifications_exist |=
997        !(*delta)->response_cookie_modifications.empty();
998  }
999  if (!cookie_modifications_exist)
1000    return;
1001
1002  // Only create a copy if we really want to modify the response headers.
1003  if (override_response_headers->get() == NULL) {
1004    *override_response_headers = new net::HttpResponseHeaders(
1005        original_response_headers->raw_headers());
1006  }
1007
1008  ParsedResponseCookies cookies =
1009      GetResponseCookies(*override_response_headers);
1010
1011  bool modified = false;
1012  modified |= MergeAddResponseCookieModifications(deltas, &cookies);
1013  modified |= MergeEditResponseCookieModifications(deltas, &cookies);
1014  modified |= MergeRemoveResponseCookieModifications(deltas, &cookies);
1015
1016  // Store new value.
1017  if (modified)
1018    StoreResponseCookies(cookies, *override_response_headers);
1019}
1020
1021// Converts the key of the (key, value) pair to lower case.
1022static ResponseHeader ToLowerCase(const ResponseHeader& header) {
1023  std::string lower_key(header.first);
1024  base::StringToLowerASCII(&lower_key);
1025  return ResponseHeader(lower_key, header.second);
1026}
1027
1028// Returns the extension ID of the first extension in |deltas| that removes the
1029// request header identified by |key|.
1030static std::string FindRemoveResponseHeader(
1031    const EventResponseDeltas& deltas,
1032    const std::string& key) {
1033  std::string lower_key = base::StringToLowerASCII(key);
1034  EventResponseDeltas::const_iterator delta;
1035  for (delta = deltas.begin(); delta != deltas.end(); ++delta) {
1036    ResponseHeaders::const_iterator i;
1037    for (i = (*delta)->deleted_response_headers.begin();
1038         i != (*delta)->deleted_response_headers.end(); ++i) {
1039      if (base::StringToLowerASCII(i->first) == lower_key)
1040        return (*delta)->extension_id;
1041    }
1042  }
1043  return std::string();
1044}
1045
1046void MergeOnHeadersReceivedResponses(
1047    const EventResponseDeltas& deltas,
1048    const net::HttpResponseHeaders* original_response_headers,
1049    scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
1050    GURL* allowed_unsafe_redirect_url,
1051    extensions::WarningSet* conflicting_extensions,
1052    const net::BoundNetLog* net_log) {
1053  EventResponseDeltas::const_iterator delta;
1054
1055  // Here we collect which headers we have removed or added so far due to
1056  // extensions of higher precedence. Header keys are always stored as
1057  // lower case.
1058  std::set<ResponseHeader> removed_headers;
1059  std::set<ResponseHeader> added_headers;
1060
1061  // We assume here that the deltas are sorted in decreasing extension
1062  // precedence (i.e. decreasing extension installation time).
1063  for (delta = deltas.begin(); delta != deltas.end(); ++delta) {
1064    if ((*delta)->added_response_headers.empty() &&
1065        (*delta)->deleted_response_headers.empty()) {
1066      continue;
1067    }
1068
1069    // Only create a copy if we really want to modify the response headers.
1070    if (override_response_headers->get() == NULL) {
1071      *override_response_headers = new net::HttpResponseHeaders(
1072          original_response_headers->raw_headers());
1073    }
1074
1075    // We consider modifications as pairs of (delete, add) operations.
1076    // If a header is deleted twice by different extensions we assume that the
1077    // intention was to modify it to different values and consider this a
1078    // conflict. As deltas is sorted by decreasing extension installation order,
1079    // this takes care of precedence.
1080    bool extension_conflicts = false;
1081    std::string conflicting_header;
1082    std::string winning_extension_id;
1083    ResponseHeaders::const_iterator i;
1084    for (i = (*delta)->deleted_response_headers.begin();
1085         i != (*delta)->deleted_response_headers.end(); ++i) {
1086      if (removed_headers.find(ToLowerCase(*i)) != removed_headers.end()) {
1087        winning_extension_id = FindRemoveResponseHeader(deltas, i->first);
1088        conflicting_header = i->first;
1089        extension_conflicts = true;
1090        break;
1091      }
1092    }
1093
1094    // Now execute the modifications if there were no conflicts.
1095    if (!extension_conflicts) {
1096      // Delete headers
1097      {
1098        for (i = (*delta)->deleted_response_headers.begin();
1099             i != (*delta)->deleted_response_headers.end(); ++i) {
1100          (*override_response_headers)->RemoveHeaderLine(i->first, i->second);
1101          removed_headers.insert(ToLowerCase(*i));
1102        }
1103      }
1104
1105      // Add headers.
1106      {
1107        for (i = (*delta)->added_response_headers.begin();
1108             i != (*delta)->added_response_headers.end(); ++i) {
1109          ResponseHeader lowercase_header(ToLowerCase(*i));
1110          if (added_headers.find(lowercase_header) != added_headers.end())
1111            continue;
1112          added_headers.insert(lowercase_header);
1113          (*override_response_headers)->AddHeader(i->first + ": " + i->second);
1114        }
1115      }
1116      net_log->AddEvent(
1117          net::NetLog::TYPE_CHROME_EXTENSION_MODIFIED_HEADERS,
1118          CreateNetLogExtensionIdCallback(delta->get()));
1119    } else {
1120      conflicting_extensions->insert(
1121          extensions::Warning::CreateResponseHeaderConflictWarning(
1122              (*delta)->extension_id, winning_extension_id,
1123              conflicting_header));
1124      net_log->AddEvent(
1125          net::NetLog::TYPE_CHROME_EXTENSION_IGNORED_DUE_TO_CONFLICT,
1126          CreateNetLogExtensionIdCallback(delta->get()));
1127    }
1128  }
1129
1130  MergeCookiesInOnHeadersReceivedResponses(deltas, original_response_headers,
1131      override_response_headers, conflicting_extensions, net_log);
1132
1133  GURL new_url;
1134  MergeRedirectUrlOfResponses(
1135      deltas, &new_url, conflicting_extensions, net_log);
1136  if (new_url.is_valid()) {
1137    // Only create a copy if we really want to modify the response headers.
1138    if (override_response_headers->get() == NULL) {
1139      *override_response_headers = new net::HttpResponseHeaders(
1140          original_response_headers->raw_headers());
1141    }
1142    (*override_response_headers)->ReplaceStatusLine("HTTP/1.1 302 Found");
1143    (*override_response_headers)->RemoveHeader("location");
1144    (*override_response_headers)->AddHeader("Location: " + new_url.spec());
1145    // Explicitly mark the URL as safe for redirection, to prevent the request
1146    // from being blocked because of net::ERR_UNSAFE_REDIRECT.
1147    *allowed_unsafe_redirect_url = new_url;
1148  }
1149}
1150
1151bool MergeOnAuthRequiredResponses(
1152    const EventResponseDeltas& deltas,
1153    net::AuthCredentials* auth_credentials,
1154    extensions::WarningSet* conflicting_extensions,
1155    const net::BoundNetLog* net_log) {
1156  CHECK(auth_credentials);
1157  bool credentials_set = false;
1158  std::string winning_extension_id;
1159
1160  for (EventResponseDeltas::const_iterator delta = deltas.begin();
1161       delta != deltas.end();
1162       ++delta) {
1163    if (!(*delta)->auth_credentials.get())
1164      continue;
1165    bool different =
1166        auth_credentials->username() !=
1167            (*delta)->auth_credentials->username() ||
1168        auth_credentials->password() != (*delta)->auth_credentials->password();
1169    if (credentials_set && different) {
1170      conflicting_extensions->insert(
1171          extensions::Warning::CreateCredentialsConflictWarning(
1172              (*delta)->extension_id, winning_extension_id));
1173      net_log->AddEvent(
1174          net::NetLog::TYPE_CHROME_EXTENSION_IGNORED_DUE_TO_CONFLICT,
1175          CreateNetLogExtensionIdCallback(delta->get()));
1176    } else {
1177      net_log->AddEvent(
1178          net::NetLog::TYPE_CHROME_EXTENSION_PROVIDE_AUTH_CREDENTIALS,
1179          CreateNetLogExtensionIdCallback(delta->get()));
1180      *auth_credentials = *(*delta)->auth_credentials;
1181      credentials_set = true;
1182      winning_extension_id = (*delta)->extension_id;
1183    }
1184  }
1185  return credentials_set;
1186}
1187
1188void ClearCacheOnNavigation() {
1189  if (content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
1190    ClearCacheOnNavigationOnUI();
1191  } else {
1192    content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
1193                                     base::Bind(&ClearCacheOnNavigationOnUI));
1194  }
1195}
1196
1197void NotifyWebRequestAPIUsed(
1198    void* browser_context_id,
1199    scoped_refptr<const extensions::Extension> extension) {
1200  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
1201  content::BrowserContext* browser_context =
1202      reinterpret_cast<content::BrowserContext*>(browser_context_id);
1203  if (!extensions::ExtensionsBrowserClient::Get()->IsValidContext(
1204      browser_context))
1205    return;
1206
1207  extensions::RuntimeData* runtime_data =
1208      extensions::ExtensionSystem::Get(browser_context)->runtime_data();
1209  if (runtime_data->HasUsedWebRequest(extension.get()))
1210    return;
1211  runtime_data->SetHasUsedWebRequest(extension.get(), true);
1212
1213  for (content::RenderProcessHost::iterator it =
1214           content::RenderProcessHost::AllHostsIterator();
1215       !it.IsAtEnd(); it.Advance()) {
1216    content::RenderProcessHost* host = it.GetCurrentValue();
1217    if (host->GetBrowserContext() == browser_context)
1218      SendExtensionWebRequestStatusToHost(host);
1219  }
1220}
1221
1222void SendExtensionWebRequestStatusToHost(content::RenderProcessHost* host) {
1223  content::BrowserContext* browser_context = host->GetBrowserContext();
1224  if (!browser_context)
1225    return;
1226
1227  bool webrequest_used = false;
1228  const extensions::ExtensionSet& extensions =
1229      extensions::ExtensionRegistry::Get(browser_context)->enabled_extensions();
1230  extensions::RuntimeData* runtime_data =
1231      extensions::ExtensionSystem::Get(browser_context)->runtime_data();
1232  for (extensions::ExtensionSet::const_iterator it = extensions.begin();
1233       !webrequest_used && it != extensions.end();
1234       ++it) {
1235    webrequest_used |= runtime_data->HasUsedWebRequest(it->get());
1236  }
1237
1238  host->Send(new ExtensionMsg_UsingWebRequestAPI(webrequest_used));
1239}
1240
1241// Converts the |name|, |value| pair of a http header to a HttpHeaders
1242// dictionary. Ownership is passed to the caller.
1243base::DictionaryValue* CreateHeaderDictionary(
1244    const std::string& name, const std::string& value) {
1245  base::DictionaryValue* header = new base::DictionaryValue();
1246  header->SetString(keys::kHeaderNameKey, name);
1247  if (base::IsStringUTF8(value)) {
1248    header->SetString(keys::kHeaderValueKey, value);
1249  } else {
1250    header->Set(keys::kHeaderBinaryValueKey,
1251                StringToCharList(value));
1252  }
1253  return header;
1254}
1255
1256#define ARRAYEND(array) (array + arraysize(array))
1257
1258bool IsRelevantResourceType(ResourceType type) {
1259  ResourceType* iter =
1260      std::find(kResourceTypeValues,
1261                kResourceTypeValues + kResourceTypeValuesLength,
1262                type);
1263  return iter != (kResourceTypeValues + kResourceTypeValuesLength);
1264}
1265
1266const char* ResourceTypeToString(ResourceType type) {
1267  ResourceType* iter =
1268      std::find(kResourceTypeValues,
1269                kResourceTypeValues + kResourceTypeValuesLength,
1270                type);
1271  if (iter == (kResourceTypeValues + kResourceTypeValuesLength))
1272    return "other";
1273
1274  return kResourceTypeStrings[iter - kResourceTypeValues];
1275}
1276
1277bool ParseResourceType(const std::string& type_str,
1278                       ResourceType* type) {
1279  const char** iter =
1280      std::find(kResourceTypeStrings,
1281                kResourceTypeStrings + kResourceTypeStringsLength,
1282                type_str);
1283  if (iter == (kResourceTypeStrings + kResourceTypeStringsLength))
1284    return false;
1285  *type = kResourceTypeValues[iter - kResourceTypeStrings];
1286  return true;
1287}
1288
1289}  // namespace extension_web_request_api_helpers
1290