| /* |
| * Copyright 2011 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| // Author: sligocki@google.com (Shawn Ligocki) |
| |
| #include <list> |
| |
| #include "net/instaweb/rewriter/public/file_load_mapping.h" |
| #include "net/instaweb/rewriter/public/file_load_policy.h" |
| #include "net/instaweb/rewriter/public/file_load_rule.h" |
| #include "pagespeed/kernel/base/string.h" |
| #include "pagespeed/kernel/base/string_util.h" |
| #include "pagespeed/kernel/http/content_type.h" |
| #include "pagespeed/kernel/http/google_url.h" |
| #include "pagespeed/kernel/util/re2.h" |
| |
| namespace net_instaweb { |
| |
| FileLoadPolicy::~FileLoadPolicy() { |
| for (FileLoadMappings::const_iterator it = file_load_mappings_.begin(); |
| it != file_load_mappings_.end(); ++it) { |
| (*it)->DecrementRefs(); |
| } |
| for (FileLoadRules::const_iterator it = file_load_rules_.begin(); |
| it != file_load_rules_.end(); ++it) { |
| (*it)->DecrementRefs(); |
| } |
| } |
| |
| // Figure out whether our rules say to load this url from file, ignoring content |
| // type restrictions for the moment. |
| bool FileLoadPolicy::ShouldLoadFromFileHelper(const GoogleUrl& url, |
| GoogleString* filename) const { |
| if (!url.IsWebValid()) { |
| return false; |
| } |
| |
| const StringPiece url_string = url.AllExceptQuery(); |
| if (!url_string.empty()) { |
| // TODO(sligocki): Consider layering a cache over this lookup. |
| // Note: Later associations take precedence over earlier ones. |
| for (FileLoadMappings::const_reverse_iterator mappings_iter = |
| file_load_mappings_.rbegin(); |
| mappings_iter != file_load_mappings_.rend(); ++mappings_iter) { |
| if ((*mappings_iter)->Substitute(url_string, filename)) { |
| // GoogleUrl will decode most %XX escapes, but it does not convert |
| // "%20" -> " " which has come up often. |
| GlobalReplaceSubstring("%20", " ", filename); |
| |
| // We know know what file this url should map to, and we want know |
| // whether this one is safe to load directly or whether we need to back |
| // off and load through HTTP. By default a mapping set up with |
| // Associate() permits direct loading of anything it applies to, but |
| // AddRule() lets people add exceptions. See if any exceptions apply. |
| for (FileLoadRules::const_reverse_iterator rules_iter = |
| file_load_rules_.rbegin(); |
| rules_iter != file_load_rules_.rend(); ++rules_iter) { |
| const FileLoadRule::Classification classification = |
| (*rules_iter)->Classify(*filename); |
| if (classification == FileLoadRule::kAllowed) { |
| return true; // Whitelist entry: load directly. |
| } else if (classification == FileLoadRule::kDisallowed) { |
| return false; // Blacklist entry: fall back to HTTP. |
| } |
| } |
| return true; // No exception applied; default allow. |
| } |
| } |
| } |
| return false; // No mapping found, no file to load from. |
| } |
| |
| bool FileLoadPolicy::ShouldLoadFromFile(const GoogleUrl& url, |
| GoogleString* filename) const { |
| const bool should_load = ShouldLoadFromFileHelper(url, filename); |
| if (!should_load) { |
| return false; |
| } |
| |
| // We could now load it from file, but if the extension is unrecognized we |
| // won't have a content type. We want to always serve with content type, so |
| // filter those out. This also lets us limit to static resources, which are |
| // the only content types we want to handle. |
| const ContentType* content_type = NameExtensionToContentType(*filename); |
| return content_type != NULL && content_type->IsLikelyStaticResource(); |
| } |
| |
| bool FileLoadPolicy::AddRule(const GoogleString& rule_str, bool is_regexp, |
| bool allow, GoogleString* error) { |
| FileLoadRule* rule = NULL; |
| if (is_regexp) { |
| const RE2 re(rule_str); |
| if (!re.ok()) { |
| error->assign(re.error()); |
| return false; |
| } |
| rule = new FileLoadRuleRegexp(rule_str, allow); |
| } else { |
| rule = new FileLoadRuleLiteral(rule_str, allow); |
| } |
| file_load_rules_.push_back(rule); |
| return true; |
| } |
| |
| bool FileLoadPolicy::AssociateRegexp(StringPiece url_regexp, |
| StringPiece filename_prefix, |
| GoogleString* error) { |
| GoogleString url_regexp_str, filename_prefix_str; |
| |
| url_regexp.CopyToString(&url_regexp_str); |
| filename_prefix.CopyToString(&filename_prefix_str); |
| |
| if (!url_regexp.starts_with("^")) { |
| error->assign("File mapping regular expression must match beginning " |
| "of string. (Must start with '^'.)"); |
| return false; |
| } |
| |
| const RE2 re(url_regexp_str); |
| if (!re.ok()) { |
| error->assign(re.error()); |
| return false; |
| } else if (!re.CheckRewriteString(filename_prefix_str, error)) { |
| return false; |
| } |
| |
| file_load_mappings_.push_back( |
| new FileLoadMappingRegexp(url_regexp_str, filename_prefix_str)); |
| |
| return true; |
| } |
| |
| void FileLoadPolicy::Associate(StringPiece url_prefix_in, |
| StringPiece filename_prefix_in) { |
| GoogleString url_prefix, filename_prefix; |
| |
| url_prefix_in.CopyToString(&url_prefix); |
| filename_prefix_in.CopyToString(&filename_prefix); |
| |
| // Make sure these are directories. Add a terminal slashes if absent. |
| EnsureEndsInSlash(&url_prefix); |
| EnsureEndsInSlash(&filename_prefix); |
| |
| // TODO(sligocki): Should fail if filename_prefix doesn't start with '/'? |
| |
| file_load_mappings_.push_back( |
| new FileLoadMappingLiteral(url_prefix, filename_prefix)); |
| } |
| |
| void FileLoadPolicy::Merge(const FileLoadPolicy& other) { |
| for (FileLoadMappings::const_iterator it = other.file_load_mappings_.begin(); |
| it != other.file_load_mappings_.end(); ++it) { |
| // Copy associations over. |
| (*it)->IncrementRefs(); |
| file_load_mappings_.push_back(*it); |
| } |
| |
| for (FileLoadRules::const_iterator it = other.file_load_rules_.begin(); |
| it != other.file_load_rules_.end(); ++it) { |
| // Copy rules over. |
| (*it)->IncrementRefs(); |
| file_load_rules_.push_back(*it); |
| } |
| } |
| |
| } // namespace net_instaweb |