1dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen// Copyright (c) 2009 The Chromium Authors. All rights reserved. 2dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen// Use of this source code is governed by a BSD-style license that can be 3dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen// found in the LICENSE file. 4dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 5dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include "net/tools/flip_server/mem_cache.h" 6dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 7dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include <dirent.h> 8dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include <errno.h> 9dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include <fcntl.h> 10dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include <stdio.h> 11dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include <sys/stat.h> 12dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include <sys/types.h> 13dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 14dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include <deque> 15dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 16dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include "base/string_piece.h" 17dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include "net/tools/dump_cache/url_to_filename_encoder.h" 18dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include "net/tools/dump_cache/url_utilities.h" 19dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include "net/tools/flip_server/balsa_frame.h" 20dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#include "net/tools/flip_server/balsa_headers.h" 21dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 22dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen// The directory where cache locates); 23dc0f95d653279beabeb9817299e2902918ba123eKristian Monsenstd::string FLAGS_cache_base_dir = "."; 24dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 25dc0f95d653279beabeb9817299e2902918ba123eKristian Monsennamespace net { 26dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 27dc0f95d653279beabeb9817299e2902918ba123eKristian Monsenvoid StoreBodyAndHeadersVisitor::ProcessBodyData(const char *input, 28dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen size_t size) { 29dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen body.append(input, size); 30dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen} 31dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 32dc0f95d653279beabeb9817299e2902918ba123eKristian Monsenvoid StoreBodyAndHeadersVisitor::HandleHeaderError(BalsaFrame* framer) { 33dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen HandleError(); 34dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen} 35dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 36dc0f95d653279beabeb9817299e2902918ba123eKristian Monsenvoid StoreBodyAndHeadersVisitor::HandleHeaderWarning(BalsaFrame* framer) { 37dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen HandleError(); 38dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen} 39dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 40dc0f95d653279beabeb9817299e2902918ba123eKristian Monsenvoid StoreBodyAndHeadersVisitor::HandleChunkingError(BalsaFrame* framer) { 41dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen HandleError(); 42dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen} 43dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 44dc0f95d653279beabeb9817299e2902918ba123eKristian Monsenvoid StoreBodyAndHeadersVisitor::HandleBodyError(BalsaFrame* framer) { 45dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen HandleError(); 46dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen} 47dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 48dc0f95d653279beabeb9817299e2902918ba123eKristian MonsenFileData::FileData(BalsaHeaders* h, const std::string& b) 49dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen : headers(h), body(b) { 50dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen} 51dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 52dc0f95d653279beabeb9817299e2902918ba123eKristian MonsenFileData::FileData() {} 53dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 54dc0f95d653279beabeb9817299e2902918ba123eKristian MonsenFileData::~FileData() {} 55dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 56dc0f95d653279beabeb9817299e2902918ba123eKristian Monsenvoid FileData::CopyFrom(const FileData& file_data) { 57dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen headers = new BalsaHeaders; 58dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen headers->CopyFrom(*(file_data.headers)); 59dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen filename = file_data.filename; 60dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen related_files = file_data.related_files; 61dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen body = file_data.body; 62dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 63dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 64dc0f95d653279beabeb9817299e2902918ba123eKristian MonsenMemoryCache::MemoryCache() {} 65dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 66dc0f95d653279beabeb9817299e2902918ba123eKristian MonsenMemoryCache::~MemoryCache() {} 67dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 68dc0f95d653279beabeb9817299e2902918ba123eKristian Monsenvoid MemoryCache::CloneFrom(const MemoryCache& mc) { 69dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen for (Files::const_iterator i = mc.files_.begin(); 70dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen i != mc.files_.end(); 71dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen ++i) { 72dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen Files::iterator out_i = 73dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen files_.insert(make_pair(i->first, FileData())).first; 74dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen out_i->second.CopyFrom(i->second); 75dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen cwd_ = mc.cwd_; 76dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 77dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen} 78dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 79dc0f95d653279beabeb9817299e2902918ba123eKristian Monsenvoid MemoryCache::AddFiles() { 80dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen std::deque<std::string> paths; 81dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen cwd_ = FLAGS_cache_base_dir; 82dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen paths.push_back(cwd_ + "/GET_"); 83dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen DIR* current_dir = NULL; 84dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen while (!paths.empty()) { 85dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen while (current_dir == NULL && !paths.empty()) { 86dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen std::string current_dir_name = paths.front(); 87dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen VLOG(1) << "Attempting to open dir: \"" << current_dir_name << "\""; 88dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen current_dir = opendir(current_dir_name.c_str()); 89dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen paths.pop_front(); 90dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 91dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen if (current_dir == NULL) { 92dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen perror("Unable to open directory. "); 93dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen current_dir_name.clear(); 94dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen continue; 95dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 96dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 97dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen if (current_dir) { 98dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen VLOG(1) << "Succeeded opening"; 99dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen for (struct dirent* dir_data = readdir(current_dir); 100dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen dir_data != NULL; 101dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen dir_data = readdir(current_dir)) { 102dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen std::string current_entry_name = 103dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen current_dir_name + "/" + dir_data->d_name; 104dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen if (dir_data->d_type == DT_REG) { 105dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen VLOG(1) << "Found file: " << current_entry_name; 106dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen ReadAndStoreFileContents(current_entry_name.c_str()); 107dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } else if (dir_data->d_type == DT_DIR) { 108dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen VLOG(1) << "Found subdir: " << current_entry_name; 109dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen if (std::string(dir_data->d_name) != "." && 110dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen std::string(dir_data->d_name) != "..") { 111dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen VLOG(1) << "Adding to search path: " << current_entry_name; 112dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen paths.push_front(current_entry_name); 113dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 114dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 115dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 116dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen VLOG(1) << "Oops, no data left. Closing dir."; 117dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen closedir(current_dir); 118dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen current_dir = NULL; 119dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 120dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 121dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 122dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen} 123dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 124dc0f95d653279beabeb9817299e2902918ba123eKristian Monsenvoid MemoryCache::ReadToString(const char* filename, std::string* output) { 125dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen output->clear(); 126dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen int fd = open(filename, 0, "r"); 127dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen if (fd == -1) 128dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen return; 129dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen char buffer[4096]; 130dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen ssize_t read_status = read(fd, buffer, sizeof(buffer)); 131dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen while (read_status > 0) { 132dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen output->append(buffer, static_cast<size_t>(read_status)); 133dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen do { 134dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen read_status = read(fd, buffer, sizeof(buffer)); 135dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } while (read_status <= 0 && errno == EINTR); 136dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 137dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen close(fd); 138dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen} 139dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 140dc0f95d653279beabeb9817299e2902918ba123eKristian Monsenvoid MemoryCache::ReadAndStoreFileContents(const char* filename) { 141dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen StoreBodyAndHeadersVisitor visitor; 142dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen BalsaFrame framer; 143dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen framer.set_balsa_visitor(&visitor); 144dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen framer.set_balsa_headers(&(visitor.headers)); 145dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen std::string filename_contents; 146dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen ReadToString(filename, &filename_contents); 147dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 148dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen // Ugly hack to make everything look like 1.1. 149dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen if (filename_contents.find("HTTP/1.0") == 0) 150dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen filename_contents[7] = '1'; 151dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 152dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen size_t pos = 0; 153dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen size_t old_pos = 0; 154dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen while (true) { 155dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen old_pos = pos; 156dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen pos += framer.ProcessInput(filename_contents.data() + pos, 157dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen filename_contents.size() - pos); 158dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen if (framer.Error() || pos == old_pos) { 159dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen LOG(ERROR) << "Unable to make forward progress, or error" 160dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen " framing file: " << filename; 161dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen if (framer.Error()) { 162dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen LOG(INFO) << "********************************************ERROR!"; 163dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen return; 164dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 165dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen return; 166dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 167dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen if (framer.MessageFullyRead()) { 168dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen // If no Content-Length or Transfer-Encoding was captured in the 169dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen // file, then the rest of the data is the body. Many of the captures 170dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen // from within Chrome don't have content-lengths. 171dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen if (!visitor.body.length()) 172dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen visitor.body = filename_contents.substr(pos); 173dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen break; 174dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 175dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 176dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen visitor.headers.RemoveAllOfHeader("content-length"); 177dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen visitor.headers.RemoveAllOfHeader("transfer-encoding"); 178dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen visitor.headers.RemoveAllOfHeader("connection"); 179dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen visitor.headers.AppendHeader("transfer-encoding", "chunked"); 180dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen visitor.headers.AppendHeader("connection", "keep-alive"); 181dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 182dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen // Experiment with changing headers for forcing use of cached 183dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen // versions of content. 184dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen // TODO(mbelshe) REMOVE ME 185dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#if 0 186dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen // TODO(mbelshe) append current date. 187dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen visitor.headers.RemoveAllOfHeader("date"); 188dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen if (visitor.headers.HasHeader("expires")) { 189dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen visitor.headers.RemoveAllOfHeader("expires"); 190dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen visitor.headers.AppendHeader("expires", 191dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen "Fri, 30 Aug, 2019 12:00:00 GMT"); 192dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 193dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen#endif 194dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen BalsaHeaders* headers = new BalsaHeaders; 195dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen headers->CopyFrom(visitor.headers); 196dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen std::string filename_stripped = std::string(filename).substr(cwd_.size() + 1); 197dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen LOG(INFO) << "Adding file (" << visitor.body.length() << " bytes): " 198dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen << filename_stripped; 199dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen files_[filename_stripped] = FileData(); 200dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen FileData& fd = files_[filename_stripped]; 201dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen fd = FileData(headers, visitor.body); 202dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen fd.filename = std::string(filename_stripped, 203dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen filename_stripped.find_first_of('/')); 204dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen} 205dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 206dc0f95d653279beabeb9817299e2902918ba123eKristian MonsenFileData* MemoryCache::GetFileData(const std::string& filename) { 207dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen Files::iterator fi = files_.end(); 208dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen if (filename.compare(filename.length() - 5, 5, ".html", 5) == 0) { 209dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen std::string new_filename(filename.data(), filename.size() - 5); 210dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen new_filename += ".http"; 211dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen fi = files_.find(new_filename); 212dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 213dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen if (fi == files_.end()) 214dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen fi = files_.find(filename); 215dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 216dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen if (fi == files_.end()) { 217dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen return NULL; 218dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 219dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen return &(fi->second); 220dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen} 221dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 222dc0f95d653279beabeb9817299e2902918ba123eKristian Monsenbool MemoryCache::AssignFileData(const std::string& filename, 223dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen MemCacheIter* mci) { 224dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen mci->file_data = GetFileData(filename); 225dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen if (mci->file_data == NULL) { 226dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen LOG(ERROR) << "Could not find file data for " << filename; 227dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen return false; 228dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen } 229dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen return true; 230dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen} 231dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 232dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen} // namespace net 233dc0f95d653279beabeb9817299e2902918ba123eKristian Monsen 234