Help
RSS
API
Feed
Maltego
Contact
Domain > results.wimsey.co
×
More information on this domain is in
AlienVault OTX
Is this malicious?
Yes
No
DNS Resolutions
Date
IP Address
2021-10-21
13.227.40.104
(
ClassC
)
2026-02-17
18.161.6.26
(
ClassC
)
Port 80
HTTP/1.1 200 OKContent-Type: text/htmlContent-Length: 60230Connection: keep-aliveDate: Tue, 17 Feb 2026 11:10:57 GMTLast-Modified: Thu, 25 Sep 2025 03:18:14 GMTETag: 83cae220fd7e7f018050b6ca1fce5014x-amz-server-side-encryption: AES256x-amz-meta-s3cmd-attrs: atime:1758750387/ctime:1758770277/gid:1000/gname:sl/md5:83cae220fd7e7f018050b6ca1fce5014/mode:33204/mtime:1758770277/uid:1000/uname:slx-amz-version-id: b_VPmPat1F_iDX12Ve72wTih8aS.vmDfAccept-Ranges: bytesServer: AmazonS3X-Cache: Error from cloudfrontVia: 1.1 ec27e2bbc77d9805bead471453d2094c.cloudfront.net (CloudFront)X-Amz-Cf-Pop: HIO52-P1X-Amz-Cf-Id: eDm-KjsaX9ItGOhr5S_l6cbzJvQMzXlqUvcRVcOA26rvDPb3g30GNw !DOCTYPE html>!-- vim: syntaxjavascript -->!--S3 Bucket Static Website index.html to display RaceResult filesCopyright(c) 2018-2025 Stuart.Lynne@gmail.comgit@bitbucket.org:stuartlynne/raceresults.git N.B. This index.html file assumes the following: - used on an Amazon S3 bucket configured as a static webserver - the S3 static webserver index and error documents both point at index.htmlN.B. HTTPS - CloudFront is used to provide HTTPS access - files have cache times set on upload - CloudFront is configured for compressingLast modified: Thu Jun 26 06:00:43 PM PDT 2025 101-->html>head> title>RaceResults/title>!-- Google Analytics -->script async srchttps://www.googletagmanager.com/gtag/js?idUA-64955454-2>/script>script> window.dataLayer window.dataLayer || ; function gtag(){dataLayer.push(arguments);} gtag(config, UA-64955454-2); gtag(event, page_view, { send_to: UA-64955454-2 });gtag(js, new Date()); /script>!-- Google Custom Search Engine This supports the use of a Google Custom Search engine. For this to work well it is important that Google robots like our site. To ensure that we need to do twothings: 1. periodically verify that GSE can run this page, /index.html and not have an issues with the Javascript. See the fetchmd() function for comments on one issue encountered 2019-04. 2. periodically generate and upload a valid robots.txt file.C.f.https://stackoverflow.com/questions/54408012/google-search-console-error-uncaught-syntaxerror-unexpected-token-functionTo test this use the Google Search Console: https://search.google.com/search-console?resource_idsc-domain%3Aresults.wimsey.co&hlen-GBGo to URL Inspection, enter results.wimsey.co/index.html and then test live url.-->!--script> (function() { var cx 013477625228922489518:od4vbi02-og; var gcse document.createElement(script); gcse.type text/javascript; gcse.async true; gcse.src https://cse.google.com/cse.js?cx + cx; var s document.getElementsByTagName(script)0; s.parentNode.insertBefore(gcse, s); })();/script>!-- load javascript dependencies, jquery, marked and bootstrap -->script defer srchttps://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js>/script>script defer srchttps://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js>/script>script defer srchttps://cdn.jsdelivr.net/npm/marked/marked.min.js>/script>!-- favicon -->meta charsetutf-8>meta nameviewport contentwidthdevice-width, initial-scale1, maximum-scale1, user-scalable0>link relapple-touch-icon sizes57x57 href/icons/apple-icon-57x57.png>link relapple-touch-icon sizes60x60 href/icons/apple-icon-60x60.png>link relapple-touch-icon sizes72x72 href/icons/apple-icon-72x72.png>link relapple-touch-icon sizes76x76 href/icons/apple-icon-76x76.png>link relapple-touch-icon sizes114x114 href/icons/apple-icon-114x114.png>link relapple-touch-icon sizes120x120 href/icons/apple-icon-120x120.png>link relapple-touch-icon sizes144x144 href/icons/apple-icon-144x144.png>link relapple-touch-icon sizes152x152 href/icons/apple-icon-152x152.png>link relapple-touch-icon sizes180x180 href/icons/apple-icon-180x180.png>link relicon typeimage/png sizes192x192 href/icons/android-icon-192x192.png>link relicon typeimage/png sizes32x32 href/icons/favicon-32x32.png>link relicon typeimage/png sizes96x96 href/icons/favicon-96x96.png>link relicon typeimage/png sizes16x16 href/icons/favicon-16x16.png>link relmanifest href/icons/manifest.json>!-- link relstylesheet href/css/results.css >-->meta namemsapplication-TileColor content#ffffff>meta namemsapplication-TileImage content/ms-icon-144x144.png>meta nametheme-color content#ffffff>/head>body onload loaded() >!-- BUCKET_URL - S3 Configuration EXCLUDE_FILE - list of files not to display EXCLUDE_DIR - list of dirs not to display EXCLUDE_EXT - list of extensions to not display-->script typetext/javascript> var BUCKET_URL https://wimseyraceresults.s3-us-west-2.amazonaws.com; // XXX testing // index.html can be uploaded to s3://wimseytestresults: // http://wimseytestresults.s3-website-us-west-2.amazonaws.com // // It will still access the files from https://wimseyracereults etc. // This URL can be used to test with non-live data. //var BUCKET_URL https://wimseytestresults.s3-us-west-2.amazonaws.com; /* exclude the following files, directories and files with specified extensions */ var EXCLUDE_FILE Makefile, index.html,test.html,list.js, qrcode.html, md.html, Title.md, Credits.md, robots.txt, sitemap.txt, sitemap.xml, sponsors.json, quotes.json ; var EXCLUDE_PREFIX photos-; var EXCLUDE_DIR javascript, icons, photos, css; var EXCLUDE_EXT css, jpg, cgi, png, xml;/script>!-- Define the divs that will receive the HTML output to display -->div idbackListing> /div>div idtitle stylemargin-top: 40px> /div>div idgcse> /div>div iddirsListing> /div>div idtopRegistrationListing> /div>div idcommuniqueListing> /div>div ideventListing> /div>div idpressListing> /div>div idseriesListing> /div>div idstartListing> /div>div idothersListing> /div>div iddocsListing> /div>div idregistrationListing> /div>div idcredits> /div>div idsponsors> /div>div idquote> /div>!-- Makes the list line size slightly larger -->style typetext/css mediascreen> li{ margin: 10px 0; } /style>!-- Make the font size responsive -->style typetext/css mediascreen> @charset utf-8; /* CSS Document */ /* Smartphones (portrait and landscape) ----------- */ //@media only screen { body {color: red; background-color: lightblue; font-size: 700%} } /* Desktop ---------------------------------------- */ //@media only screen and (min-device-width : 992px) { body { font-size: 120%; background-color: gold; } } @media only screen and (min-device-width : 992px) { body { font-size: 120%; background-color: white; } } /* start of large tablet styles */ //@media screen and (max-width: 991px) { body { font-size: 150%; background-color: lightblue; } } @media screen and (max-width: 991px) { body { font-size: 150%; background-color: white; } } /* start of medium tablet styles */ //@media screen and (max-width: 767px) { body { font-size: 125%; background-color: lightgreen; } } @media screen and (max-width: 767px) { body { font-size: 125%; background-color: white; } } /* Desktop Blogger iFrame ------------------------- */ //@media only screen and (device-height : 2400px) { body { font-size: 120%; background-color: yellow; } } @media only screen and (device-height : 2400px) { body { font-size: 120%; background-color: white; } } /* start of phone styles */ //@media screen and (max-width: 479px) { body { font-size: 120%; background-color: lightsalmon; } } @media screen and (max-width: 479px) { body { font-size: 120%; background-color: white; } } /* CSS Document */ body { background: #fff; color: #444; font-family: Open Sans, sans-serif; font-size: 16px; font-weight: lighter; margin-bottom: 1em; } /* CSS for table */ .subtable { width: 100%; border-collapse: collapse; margin: 5px 0; } .subtable td { padding: 4px 8px; border-bottom: 1px solid #ddd; font-size: 90%; } /* CSS for buttons */ .xmobile-button { vertical-align: top; border: none; /* Remove border */ background-color: lightgrey; border-radius: 2px; margin: 2px 2px; height: 24px; font-size: 80%; padding: 2px 6px; cursor: pointer; border: 1px solid #ccc; /* Add a border */ } .xmobile-button { height: 40px; font-size: 90%; } .desktop-button:hover { background-color: #ddd; } .mobile-button { vertical-align: top; background-color: lightgrey; border-radius: 2px; margin: 2px 2px; height: 40px; font-size: 80%; } .desktop-button { vertical-align: top; border-radius: 2px; margin: 2px 2px; height: 24px; font-size: 60%; } /* Hover */ a.hover {text-decoration: none; color: black;} //a.hover:hover {text-decoration: underline;} /* ———————————————————————————————————————————————— Sponsors ———————————————————————————————————————————————— */ .sponsor-container { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 0.75em; margin: 0.5em 0 0.75em; text-align: center; } .sponsor-logo { flex: 1 1 180px; max-width: 220px; max-height: 90px; width: auto; height: auto; object-fit: contain; transition: opacity 0.5s ease; } /* large desktop: allow a touch more height */ @media only screen and (min-width: 1200px) { .sponsor-logo { max-height: 110px; } } /* tablets & small desktops */ @media only screen and (max-width: 991px) { .sponsor-logo { flex-basis: 160px; max-height: 80px; } } /* mobile portrait: show two logos, remove wrap stacking */ @media only screen and (max-width: 767px) and (orientation: portrait) { hr { margin: 0.25em 0; } .sponsor-container { gap: 0.5em; } .sponsor-logo { flex: 1 1 calc(50% - 0.5em); max-width: calc(50% - 0.5em); max-height: 68px; } } /* ————————————————————————————————————————— Sponsors-style Title Block ————————————————————————————————————————— */ #title { /* remove default margins/padding if any */ margin: 0.5em 0; padding: 0; } /* the actual clamped container */ .title-block { overflow: hidden; /* hide overflow */ position: relative; max-height: 100px; /* desktop default */ transition: max-height 0.3s ease; } /* A gentle “fade-out” gradient so users know there’s more above the clamp */ .title-block::after { content: ; position: absolute; bottom: 0; left: 0; right: 0; height: 3em; background: linear-gradient(to bottom, rgba(255,255,255,0), #fff); } /* tablet & small desktops: a little taller block */ @media only screen and (min-width: 768px) and (max-width: 991px) { .title-block { max-height: 50px; } } /* mobile portrait: shrink the block */ @media only screen and (max-width: 767px) and (orientation: portrait) { .title-block { max-height: 120px; } }/style>script > function isMobileDevice() { var isMobile navigator.userAgent.toLowerCase().match(/mobile/i); return isMobile ? true : false; }; function getext(filename) { if (typeof filename undefined) return ; var ext filename.split(.).pop(); return ext filename ? : ext; } function getbasename(filename) { return typeof filename undefined ? : filename.substring(0, filename.lastIndexOf(.)) || filename; } /* Race Results are displayed in a table, each race having two rows. * The first row is the name of the race and the second is normally invisible row * containing the buttons to click on to get race results or photos. * * The following are used to track which, if any, of the invisible row are * currently displayed. */ var visibleRow 0; var visibleTable ; var timeout 0; /* rowoff * Make a displayed row invisible again. */ function rowoff(tablename, id) { row document.getElementById(tablename).rowsid; if (!row) return; row.style.display none; if (visibleRow id && visibleTable tablename) { visibleRow 0; visibleTable ; } } /* rowoffdelayed * use setTimeout to call rowoff after timedelay */ function rowoffdelayed(tablename, id) { timeout setTimeout(function() {rowoff(tablename, id);}, 20000); } /* toggle * Make a button row visible. */ function toggle(tablename, id) { /* if there is a pending timeout to do a rowoff cancel it */ if (timeout) { clearTimeout(timeout); timeout 0; } /* Check if the currently visible row is or is not the same. * Do nothing (return) if the same, turn off any other. */ if (visibleTable ! ) { if (visibleRow id && visibleTable tablename) return; rowoff(visibleTable, visibleRow); } /* find and set display to to allow row to be displayed */ row document.getElementById(tablename).rowsid; if (!row) return; row.style.display row.style.display none ? : none; visibleRow id; visibleTable tablename; } function do_aref(ref, text) { return a href + ref + classhover targettop> + text + /a>; } function do_button(text, title) { var button isMobileDevice() ? mobile : desktop; //return button typebutton classbtn btn-secondary data-toggletooltip data-placementright title + title + > + text + /button>; return button typebutton class+button+-button data-toggletooltip data-placementright title + title + > + text + /button>; } function do_aref_with_button(ref, text, tooltip) { return do_aref(ref, do_button(text, tooltip)); } function old_do_file_button(ref, ext, lastModified) { lastModified lastModified.replace(/T/, ).replace(/\..*Z/, ); return do_aref_with_button(ref, ext.toUpperCase(), Uploaded: + lastModified); } function do_file_button(ref, ext, lastModified) { lastModified lastModified.replace(/T/, ).replace(/\..*Z/, ); let icon ; switch (ext.toLowerCase()) { case pdf: icon 📄; // Document break; case html: icon 🌐; // Web / browser break; case md: icon 📝; // Markdown note break; case gpx: icon 🗺️; // Map break; case xlsx: case xls: icon 📊; // Spreadsheet break; default: icon 📁; // Generic file } return do_aref_with_button(ref, `${icon} ${ext.toUpperCase()}`, Uploaded: + lastModified); } function do_qr_aref(text) { text text.replace(/^\//, ); ref location.protocol + // + location.hostname + location.pathname; return do_aref_with_button(location.origin + /qrcode.html?path + ref , text, QR Code for this page); } /* do_file_aref * Generate an aref for a file, this uses the last modified time for the tooltip */ function do_file_aref(ref, text, extension, lastModified) { //console.log(do_file_aref: ref: %s text: %s extension: %s, ref, text, extension); text text.replace(/^\//, ); lastModified lastModified.replace(/T/, ); lastModified lastModified.replace(/\..*Z/, ); /* check for markdown and do a button that uses md.html to render */ if (extension md) { ref ref.replace(/^\//, ); return do_aref_with_button(location.origin + /md.html?path + ref, text, ref + Uploaded: + lastModified); } /* check for photos- file, that gets translated back to path in the /photos- directory * for the button a ref. */ if (extension.startsWith(photos-)) { text text.replace(/photos-/, ); newref ref.replace(/\.photos-.*/, .html); newref newref.replace(/\//, ); newref newref.replace(/\//g, -); return do_aref_with_button(/ + extension + / + newref, text, ref + Uploaded: + lastModified); } /* normal file a ref, typically html, pdf, etc. */ return do_aref_with_button(ref, text, ref + Uploaded: + lastModified); } /* do_file_row * Generate a row for a file in a table, this is used to display the file name, extension, * last modified time and a link to the file. * * ref - the file reference * label - the file label, typically the basename * extension - the file extension * lastModified - the last modified time of the file */ function do_file_row(ref, label, extension, lastModified) { lastModified lastModified.replace(/T/, ).replace(/\..*Z/, ); const link do_aref(ref, View); return `tr>td>${label}/td>td>${extension.toUpperCase()}/td>td>${lastModified}/td>td>${link}/td>/tr>`; } /* fetchmd * Fetch a markdown file, render it, and return the rendered html. This is * used for Title.md and Credits.md. * * N.B. this is an asynchronous process that will eventually fill a specified the * specified div $(id).html(...) */ function fetchmd(mdfile, id, top, bottom) { mdfile location.protocol + // + location.hostname + / + mdfile; /* 2019-04-01 * N.B. response > response.text() syntax works OK for Chrome, but Google Search * engine indexing fails with: Uncaught SyntaxError: Unexpected token function. * E.g. * .then(response > response.text()) FAILS * .then(function (response) { return response.text(); }) OK * * C.f.https://stackoverflow.com/questions/54408012/google-search-console-error-uncaught-syntaxerror-unexpected-token-function * * To test this use the Google Search Console: * https://search.google.com/search-console?resource_idsc-domain%3Aresults.wimsey.co&hlen-GB * Go to URL Inspection, enter results.wimsey.co/index.html and then test live url. */ //console.log(fetchmd: %s, mdfile); fetch(mdfile) .then(function (response) { return response.text(); }) .then(function(data) { return $(# + id).html(top + marked.parse(data) + bottom); }); } function registrationLineIsActive(line, today) { var dateMatch line.match(/(\d{4}-\d{2}-\d{2})/); if (!dateMatch) return true; var eventDate new Date(dateMatch1 + T00:00:00); if (isNaN(eventDate.getTime())) return true; return eventDate.getTime() > today.getTime(); } function appendRegistrationLines(listElement, text) { if (typeof text ! string) return; var lines text.split(/\r?\n/) .map(function(line) { return line.trim(); }) .filter(function(line) { return line.length > 0; }) .filter(function(line) { return !/^#+\s*registration links$/i.test(line); }) .filter(function(line, idx) { return !(idx 0 && /^registration links$/i.test(line)); }); var today new Date(); today.setHours(0, 0, 0, 0); lines lines.filter(function(line) { return registrationLineIsActive(line, today); }); if (!lines.length) return 0; var markdown lines.map(function(line) { return /^-*+\s/.test(line) ? line : - + line; }).join(\n); var parsed $(div>).html(marked.parse(markdown)); var items parsed.find(li); if (!items.length) return 0; var appendedCount 0; items.each(function() { $(this).find(a).attr(target, _blank).attr(rel, noopener noreferrer); listElement.append($(this)); appendedCount + 1; }); return appendedCount; } function renderRegistrationLinks(files, targetId, heading) { var source Array.isArray(files) ? files.slice() : ; if (!source.length) return; source.sort(sortThings); var title heading || Registration Links; var container $(div classregistration-block>hr>h3> + title + /h3>/div>); var listElement $(ul classregistration-list>/ul>); container.append(listElement); $(targetId).empty().append(container); var pending source.length; var appended false; console.log(Registration render start, targetId, source.map(function(item) { return item.Key; })); source.forEach(function(item) { var fileUrl location.protocol + // + location.hostname + / + item.Key; fetch(fileUrl) .then(function(response) { return response.text(); }) .then(function(text) { var added appendRegistrationLines(listElement, text); console.log(Registration file processed, item.Key, added, added); if (listElement.children().length) appended true; }) .catch(function(error) { console.log(registration fetch failed: %s - %o, item.Key, error); }) .then(function() { pending - 1; if (pending 0 && !appended) { console.log(Registration render complete; removing empty container for, targetId); container.remove(); } if (pending 0 && appended) { console.log(Registration render complete with entries for, targetId); } }); }); } /* generate_dir_list * * Produce an ordered list of arefs to relative directories. E.g.: * * prefix results ref * 2018/ href2018/ * 2018/ 2018/ev2018/ hrefev2018/ * * The names hash contains names to use instead of the short series tag. */ function generate_dir_list(prefix, results, id, names, topflag) { var ol_body p>ol>; for (i 0; i results.length; i++) { dirname resultsi; dirname dirname.replace(prefix, ); dirname dirname.replace(/\/$/, ); try { if(typeof dirname ! undefined) { }} catch(e) { console.log(dirname is undefined); } text (dirname in names) ? namesdirname : dirname; ol_body + topflag ? do_aref_with_button(dirname + /, text, ) : li> + do_aref(dirname + /, text) + /li>; } ol_body + /ol>; $(id).html(ol_body); } function commonPrefix(strings) { if (strings.length 0) return ; // Start with the first string as the prefix candidate let prefix strings0; console.log(strings) console.log(prefix: %s, prefix) // Iterate through the strings starting from the second one for (let i 1; i strings.length; i++) { // Compare characters of each string with the current prefix for (let j 0; j prefix.length; j++) { // If a mismatch is found, update the prefix if (stringsij ! prefixj) { prefix prefix.slice(0, j); break; } } // If the current string is shorter than the prefix, update the prefix if (stringsi.length prefix.length) { prefix prefix.slice(0, stringsi.length); } console.log(prefix%d: %s, i, prefix) // If the prefix becomes empty, theres no common prefix if (prefix ) break; } console.log(prefix: %s, prefix) return prefix; } function normalizeEventKey(basename) { // Remove r1-, r2- suffixes basename basename.replace(/-r\d-/, ); // Match full pattern up to event name (e.g. 2025-03-22-Murchie-100PM) let match basename.match(/^(\d{4}-\d{2}-\d{2})-(^-+)/); if (match) { return match1 + - + match2; // e.g. 2025-03-22-Murchie } return basename; } function generate_file_list(section, prefix, files, id, tablename, top) { if (files.length 0) return; files.sort(sortThings); const groupMap {}; // 2025-03-22-Murchie → list of { key, ext, file } for (const file of files) { const ext getext(file.Key); let basename getbasename(file.Key).replace(prefix, ); if (basename Title && ext md) { //fetchmd(file.Key, title, , hr>); fetchmd( file.Key, title, div classtitle-block>, // top wrapper /div>hr> // close + divider ); continue; } if (basename Credits && ext md) { fetchmd(file.Key, credits, hr>, ); continue; } if (EXCLUDE_EXT.includes(ext)) continue; if (EXCLUDE_FILE.includes(basename) || EXCLUDE_FILE.includes(`${basename}.${ext}`)) continue; // Remove trailing -r1-, -r2- etc. for grouping const cleanBase basename.replace(/-r\d-/, ); const groupKey normalizeEventKey(cleanBase); if (!groupMapgroupKey) groupMapgroupKey ; groupMapgroupKey.push({ basename: cleanBase, ext: ext, key: file.Key, lastModified: file.LastModified }); } let html `${top}h3>${section}/h3>table id${tablename}>`; let rowId 1; let fid 1; for (const groupKey, entries of Object.entries(groupMap)) { const onmouseover `toggle(${tablename},${rowId})`; const onmouseleave `rowoffdelayed(${tablename},${rowId})`; // Display group entry const label groupKey.replace(/-/g, ).replace(/(\d{4}) (\d{2}) (\d{2})/, $1-$2-$3); html + `tr>td>em>${fid++}. /em>a href# classhover onmouseleave${onmouseleave} onmouseover${onmouseover}>${label}/a>/td>/tr>`; // Build subtable for this group let subtable table classsubtable>; const subgroups {}; for (const e of entries) { // Group by full filename to get 100PM vs 1030AM etc. let shortName e.basename.replace(groupKey + -, ); shortName shortName.replace(/-_/g, ).replace(/r\d-/, ); if (!subgroupsshortName) subgroupsshortName ; subgroupsshortName.push(e); } //for (const name, versions of Object.entries(subgroups)) { // subtable + `tr>td>${name}/td>td>`; // for (const v of versions) { // subtable + do_file_button(/ + v.key, v.ext, v.lastModified) + ; // } // subtable + /td>/tr>; //} const subgroupKeys Object.keys(subgroups); /* build subtable * If there is only one row, just show the icons, otherwise show name + icons. */ if (subgroupKeys.length 1) { // Only one row — omit name, show just icons const versions subgroupssubgroupKeys0; subtable + `tr>td colspan2>`; for (const v of versions) { subtable + do_file_button(/ + v.key, v.ext, v.lastModified) + ; } subtable + `/td>/tr>`; } else { // Multiple rows — show name + icons for (const name, versions of Object.entries(subgroups)) { subtable + `tr>td>${name}/td>td>`; for (const v of versions) { subtable + do_file_button(/ + v.key, v.ext, v.lastModified) + ; } subtable + /td>/tr>; } } subtable + /table>; html + `tr styledisplay:none>td colspan2>${subtable}/td>/tr>`; rowId + 2; } html + /table>; $(id).html(html); } /* button_generate_file_list * * Produce an ordered list of arefs to files. E.g.: * * prefix results ref * 2018/a.html a html hrefa.html */ function button_generate_file_list(section, prefix, files, id, tablename, top) { console.log(generate_file_list: Tablename: %s prefix: %s Files: %s, tablename, prefix, files); if (files.length 0) return ; var ol_body top + h3> + section + /h3>table id + tablename + >; var filenames ; var basenames new Map() /* last_date_rx for (var i 0; i files.length; i++) { basename getbasename(filesi.Key); basename basename.replace(prefix, ); console.log(xfiles%d: %s, i, basename); date_rx /^\d\d\d\d \d\d \d\d /; date_rx_match basename.match(date_rx); if date_rx_match ! && date_rx_match ! last_date_rx { //if last_date_rx ! { //} last_date_rx date_rx_match; filenames.push(basename); } } common_prefix commonPrefix(filenames); console.log(common_prefix: %s, common_prefix); */ /* Generate list entries, this is slightly complicated to support generating a single * list entry for multiple files that differ only in the file extension, with separate * arefs for each extension. */ var fid 1; var trid 1; var pdfs 0; var htmls 0; var first true; var count 0; var curfile ; var curdate ; for (var i 0; i files.length; i++) { console.log(files%d: %s, i, filesi.Key); /* get file extension and basename */ extension getext(filesi.Key); basename getbasename(filesi.Key); basename basename.replace(prefix, ); count++; console.log(files%d: \%s\ \%s\, i, basename, extension); console.log(---); console.log(%s:%d %s %s, section, i, basename, extension); console.log(%s:%d %s %s PHOTO: %d, section, i, basename, extension, extension.search(/photos/)); console.log(%s:%d %s %s PHOTO: %d, section, i, basename, extension, extension.indexOf(/photos/)); /* Check for Title.md and Credits.md files, render markdown in title and credit area. * N.b. These initiate asynchronous file requests to fill a specific div with data * rendered from markdown to html. */ if (basename Title && extension md) { fetchmd(filesi.Key, title, , hr>); continue; } if (basename Credits && extension md) { fetchmd(filesi.Key, credits, hr>, ); continue; } /* exclude files */ try { if(typeof EXCLUDE_EXT ! undefined) { if (EXCLUDE_EXT.includes(extension)) {continue;} } } catch(e) { console.log(EXCLUDE_EXT is undefined); } try { if(typeof extension ! undefined) { if (extension.includes(EXCLUDE_EXT)) {continue;} }} catch(e) { console.log(extension is undefined); } try { if(typeof EXCLUDE_FILE ! undefined) { if (EXCLUDE_FILE.includes(basename)) {continue;} if (EXCLUDE_FILE.includes(basename + . + extension)) {continue;} }} catch(e) { console.log(EXCLUDE_FILE is undefined); } /* get rid of everything after last dash e.g. Squamish-Elite becomes Squamish */ if (extension.search(/photos/) 0) { /* Dont display annoying CrossMgr -r1-. labels at the end of file names, * only the display name is modified. The file name is left so that the aref * will work correctly. */ basename basename.replace(/r\d-/, ); console.log(%s:%d %s %s A, section, i, basename, curdate); basename basename.replace(/-^-*$/,); console.log(%s:%d %s %s B, section, i, basename, curdate); /* if the name ends in whitespace and a number, remove, eg. WTNC UBC 730 */ basename basename.replace(/ -\d+$/, ); console.log(%s:%d %s %s C, section, i, basename, curdate); //dashes (basename.search(-)); dashes (basename.match(/-/g)); if (dashes ! null) { console.log(%s:%d dashes: %d, section, i, dashes); if (dashes > 3) { basename basename.replace(/-^-*$/, ); } } } else { console.log(%s:%d %s %s PHOTO, section, i, basename, curdate); } //console.log(basename: %s extension: %s curfile: %s, basename, extension, curfile); //console.log(basename: %s extension: %s curdate: %s, basename, extension, curdate); console.log(%s:%d %s %s C, section, i, basename, curdate); displayname basename; basename basename.replace(/-/g, ); displayname displayname.replace(/(....)-(..)-(..)-/, $1%$2%$3 ); displayname displayname.replace(/-/g, ); displayname displayname.replace(/_/g, ); displayname displayname.replace(/\+/g, ); displayname displayname.replace(/(....)%(..)%(..) /, $1-$2-$3 ); date_rx /^\d\d\d\d \d\d \d\d /; date_rx_match basename.match(date_rx); if (date_rx_match) { datename date_rx_match0; //console.log(generate_file_list%d: \%s\ date: %s, i, basename, datename); } else { datename ; } /* List entries for files are grouped, so that multiple files that differ only in the suffix are * displayed in a single entry, with multiple buttons for the different styles, e.g. html, pdf */ //if (curfile ! && (curfile.startsWith(basename) || basename.startsWith(curfile))) if (curdate ! && curdate datename) { console.log(MATCH%d: %s, i, curfile); console.log(MATCH%d: %s, i, basename); /* we may have multiple pdf and html files, so well number them */ if (extension pdf) extension + + ++pdfs; if (extension html) extension + + ++htmls; ol_body + do_file_aref(/ + filesi.Key, extension, extension, filesi.LastModified); continue; } //console.log(NOMATCH: %s, curfile); //console.log(NOMATCH: %s, basename); curfile basename; curdate datename; pdfs 0; htmls 0; /* we may have multiple pdf and html files, so well number them */ if (extension pdf) extension + + ++pdfs; if (extension html) extension + + ++htmls; if (!first) { ol_body + /td>/tr>; } if (extension ) { ol_body + tr>td>em> + fid + . /em> + do_file_aref(/ + filesi.Key, displayname, extension, filesi.LastModified) + /td>/tr>tr>td>; } else { var onmouseover \toggle(+tablename+,+trid+,false)\; var onmouseleave \rowoffdelayed(+tablename+,+trid+)\; ol_body + tr>td>em> + fid + . /em>; ol_body + a href# classhover onmouseleave+onmouseleave+ onmouseover+onmouseover+> + displayname + /a>; ol_body + /td>/tr>tr styledisplay:none>td>; ol_body + do_file_aref(/ + filesi.Key, extension, extension, filesi.LastModified); } first false; fid + 1; trid + 2; continue; } /* only display if we have found files */ if (!count) return; /* finish table */ ol_body + !first ? /td>/tr> : ; ol_body + /table>; $(id).html(ol_body); } /* getInfoFromS3Data * * This is given a xml file containing directory listing directly from the S3 bucket. * The xml file will be decomposed into a list of files and a list of directories. */ function getInfoFromS3Data(xml) { /* get files */ var files $.map(xml.find(Contents), function(item) { return { Key: $(item).find(Key).text(), LastModified: $(item).find(LastModified).text(), Type: file } } ); /* get directories */ var directories $.map(xml.find(CommonPrefixes), function(item) { return { Key: $(item).find(Prefix).text(), LastModified: , Size: 0, Type: directory } } ); var nextMarker ($(xml.find(IsTruncated)0).text() true) ? nextMarker $(xml.find(NextMarker)0).text() : null; return { files: files, directories: directories, prefix: $(xml.find(Prefix)0).text(), nextMarker: encodeURIComponent(nextMarker) } } function dotfilecheck(regex, key, include_array) { if (!key.match(regex)) return; key key.replace(regex, ); include_array.push(key); } function dotfilecheck2(regex, key, include_array) { if (!key.match(regex)) return; include_array.push(key); } //function strincludearray(string, matcharray) { // for (i 0; i matcharray.length; i++) { // if (string.includes(matcharrayi)) // return true; // } // return false; //} /* sortThings * a and b are hashes with a component called Key to compare on. */ function sortThings(a, b) { a1 a.Key.replace(/-/g, ); b1 b.Key.replace(/-/g, ); if (a1 > b1) return 1; else if (a1 b1) return -1; else if (a1 b1) return 0; } /* */ /* generate_listing * * Called to generate a listing for a specific directory. * N.B. the top level directory is reverse sorted to get most recent years at the top. * * Given the requested URL we will query the S3 bucket to get the contents of that directory. * */ function generate_listing(prefix, redirect, resultsonlyflag, topflag) { console.log(generate_listing: --------------------------) patharray prefix.split(/); redirect location.protocol + // + location.hostname + /; back ; cwd ; if (patharray.length > 2) { for (i 0; i patharray.length - 2; i++) { redirect + patharrayi + /; back + patharrayi; cwd patharrayi+1; } } else if (patharray.length > 1) { redirect /; back top; cwd patharray0; } var queryURL BUCKET_URL + /?delimiter/&prefix + prefix; console.log(queryURL: %s, queryURL); var sortReverse true; /* get queryURL initiates a connection to get the file and then returns. The * .done(...) function receives the data asynchronously. The data is processed * as received and to build up files and dirs lists. Those are then used to build * up htmn to put into the various divs. The browser will display these as * they are added to the divs. */ $.get(queryURL) .done(function(data) { var xml $(data); var communique_include ; var press_include ; var series_include ; var start_include ; var others_include ; var docs_include ; var registration_include ; var dirs_list ; var files_list ; var communique_list ; var press_list ; var series_list ; var start_list ; var others_list ; var docs_list ; var registration_list ; var files_exclude ; var dirs_exclude ; var names_exclude ; var info getInfoFromS3Data(xml); console.log(info.files: %s, info.files) /* build series and other include lists */ jQuery.each(info.files, function(idx, item) { console.log(get%d: file: %s, idx, item.Key); dotfilecheck(/^.*\/.COMMUNIQUE-/, item.Key, communique_include); dotfilecheck(/^.*\/.PRESS-/, item.Key, press_include); dotfilecheck(/^.*\/.SERIES-/, item.Key, series_include); dotfilecheck(/^.*\/.START-/, item.Key, start_include); dotfilecheck(/^.*\/.OTHER-/, item.Key, others_include); dotfilecheck(/^.*\/.DOC-/, item.Key, docs_include); dotfilecheck(/^.*\.REGISTRATION-/, item.Key, registration_include); dotfilecheck(/^.*\/.EXCLUDE-/, item.Key, files_exclude); dotfilecheck2(/^.*\/\..*-/, item.Key, names_exclude); dotfilecheck(/^.*\/.EXCLUDEDIR-/, item.Key, dirs_exclude); }); console.log(series_include %s, series_include) function getFileName(path) { return path.split(/).pop(); // Extracts only the last part of the object key } function strincludearray(string, matcharray) { for (let i 0; i matcharray.length; i++) { if (string.includes(matcharrayi)) { return true; } } return false; } jQuery.each(info.files, function(idx, item) { console.log(jQuery.each: %s, item.Key); // Skip hidden files inside directories if (item.Key.match(/\/\.^\/*$/)) { return; } console.log(jQuery.match), console.dir(item.Key); let fileName getFileName(item.Key); // Extracts the filename // Apply inclusion lists but only match the filename if (strincludearray(fileName, communique_include)) { communique_list.push(item); return; } if (strincludearray(fileName, press_include)) { press_list.push(item); return; } if (strincludearray(fileName, series_include)) { series_list.push(item); return; } if (strincludearray(fileName, start_include)) { start_list.push(item); return; } if (strincludearray(fileName, others_include)) { others_list.push(item); return; } if (strincludearray(fileName, docs_include)) { docs_list.push(item); return; } if (strincludearray(fileName, registration_include)) { registration_list.push(item); return; } // Exclusions should be checked before adding to any list if (strincludearray(fileName, files_exclude)) { return; } if (strincludearray(fileName, names_exclude)) { return; } // Handle special cases (Title.md, Credits.md, Sponsors.json) if (fileName Title.md) { fetchmd(item.Key, title, , hr>); return; } if (fileName Credits.md) { console.log(jQuery.each: fetchmd %s, item.Key); fetchmd(item.Key, credits, hr>, ); return; } if (fileName sponsors.json) { get_sponsors(item.Key); return; } let extension getext(item.Key); // Special case for photos files if (fileName.startsWith(photos-)) { console.log(info.files: %s matched photos, item.Key); return; } files_list.push(item); }); console.log(communique_list); console.dir(communique_list); console.log(press_list); console.dir(press_list); console.log(series_list); console.dir(series_list); console.log(start_list); console.dir(start_list); console.log(others_list); console.dir(others_list); console.log(docs_list); console.dir(docs_list); console.log(docs_include); console.dir(docs_include); console.log(files_list); console.dir(files_list); /* build up names hash */ names {}; for (i 0; i names_exclude.length; i++) { nameinfo names_excludei.split(-); tag nameinfo0.replace(/.*\/\./, ); namestag nameinfo1.replace(/_/g, ); } jQuery.each(info.directories, function(idx, item) { if (strincludearray(item.Key, dirs_exclude)) { return; } if (strincludearray(item.Key, EXCLUDE_DIR)) { return; } dirs_list.push(item.Key.replace(/\/$/, )); }); /* If the requested directory is empty or non-existent redirect upwards */ if (dirs_list.length 0 && files_list.length 0 && docs_list 0 && others_list 0 && communique_list 0 && press_list 0 && series_list 0 && start_list 0 ) { console.log(Empty redirection up: %s ***************, redirect); location redirect; } if (sortReverse) dirs_list dirs_list.reverse(); console.log(files list reverse sort); files_list.sort(sortThings); //files_list.reverse(); generate_dir_list(prefix, dirs_list, #dirsListing, names, topflag); if (topflag) { renderRegistrationLinks(registration_list, #topRegistrationListing, Upcoming Event Registrations); //generate_photo_list(prefix, dirs_list, #photosListing, names); } else { generate_file_list(Comminiques, prefix, communique_list, #communiqueListing, communiqueTable, hr>); generate_file_list(Press Releases, prefix, press_list, #pressListing, pressTable, hr>); generate_file_list(Series, prefix, series_list, #seriesListing, seriesTable, hr>); generate_file_list(Events, prefix, files_list, #eventListing, filesTable, p>); generate_file_list(Start Lists, prefix, start_list, #startListing, startTable, p>); generate_file_list(Other Results, prefix, others_list, #othersListing, otherTable, p>); generate_file_list(Documents, prefix, docs_list, #docsListing, docsTable, hr>); renderRegistrationLinks(registration_list, #registrationListing, Registration Links); } }) .fail(function(error) { console.log(generate_listing: get fail); console.log(generate_listing: data); console.error(error); // XXX // window.location.pathname redirect; }); var ol_body ; if (prefix ! && !resultsonlyflag) { ol_body + h2> + do_aref_with_button(redirect, back + /, ) + do_qr_aref(cwd) + /h2>; $(#backListing).html(ol_body); sortReverse false; } } /* fetchphotoinfo * Example of using promise to delay fetch */ /* async function fetchphotoinfo(photourl) { const response await fetch(photourl); const data await response.text(); return data; } */ /* start_listing * Example of using promise to delay fetch */ /* function start_listing(prefix, redirect, resultsonlyflag, topflag) { var photoURL BUCKET_URL + /?delimeter/&prefixphotos/; fetchphotoinfo(photoURL) .then(data > generate_listing(prefix, redirect, resultsonlyflag, topflag, data)); } */ /* get_quote */ function get_quote(origin) { var quotefile origin + / + quotes.json; $.ajaxSetup({cache:true}); $.getJSON(quotefile, function(data){ quotes ; $.each(data, function (index, value) { quotes.push(value); }); var random Math.floor(Math.random() * quotes.length); $(#quote).html(hr> + quotesrandom); }); } /* Sponsors support */ var tid; var sponsors ; var sponsor 0; var cursponsors , , , ; /* do_sponsor * Fade in a sponsor logo. */ function maxSponsorSlots() { var portraitMobile window.matchMedia((max-width: 767px) and (orientation: portrait)).matches; return portraitMobile ? 2 : 4; } function setSponsorVisibility() { var max maxSponsorSlots(); if (!sponsors.length) return; for (var i 0; i 4; i++) { var slot $(#sponsorId + i); if (!slot.length) continue; if (i max) { slot.css(display, ); if (!slot.attr(src)) { do_sponsor(i, false, true); } } else { slot.stop(true, true).css(display, none).attr(src, ); cursponsorsi ; } } sponsor Math.min(sponsor, max - 1); } function handleSponsorLayoutChange() { setSponsorVisibility(); } window.addEventListener(resize, handleSponsorLayoutChange); window.addEventListener(orientationchange, handleSponsorLayoutChange); function do_sponsor(slot, delay, fromVisibility) { if (!sponsors.length) return; var max maxSponsorSlots(); if (slot > max) { if (!fromVisibility) { $(#sponsorId + slot).attr(src, ); cursponsorsslot ; } return; } var id $(#sponsorId + slot); if (!id.length || !id.is(:visible)) { id.attr(src, ); cursponsorsslot ; return; } var active cursponsors.slice(0, max); var value; var guard 0; do { var random Math.floor(Math.random() * sponsors.length); value sponsorsrandom; guard++; if (guard > sponsors.length * 2) break; } while (active.indexOf(value.Sponsor) > 0); cursponsorsslot value.Sponsor; id.css(min-height, id.height()); id.fadeOut(delay ? 5000 : 0, function(){ id.attr(src, value.URL); id.attr(alt, value.Sponsor); id.fadeIn(delay ? 5000 : 0); }); } /* do_sponsors * Called from interval timer to load a new logo */ function do_sponsors() { var max maxSponsorSlots(); do_sponsor(sponsor); sponsor (sponsor + 1) % max; } /* get_sponsors * Called to load a sponsor json file. This will create the sponsors div * and set up interval timer to periodically change the displayed logos. */ function get_sponsors(sponsorfile) { var sponsorsfile location.origin + / + sponsorfile; //console.log(get_sponsors: %s, sponsorsfile); /* fetch the sponsor json file, unpack and start timer */ $.ajaxSetup({cache:true}); $.getJSON(sponsorsfile, function(data){ /* This is done after file data is available, * first iterate across array to build sponsors array. * N.B. we use weight to add extra entries to bias the * number of times a specific sponsor logo will be displayed. */ sponsors ; $.each(data, function (index, value) { for (i 0; i value.Weight; i++) { //console.log(get_sponsors%d:%s:%d %s, index, value.Weight, i, value.URL); sponsors.push(value); } }); /* set up the html, seed the logo slots, and start the interval timer */ let html hr>div classsponsor-container>; for (var idx 0; idx 4; idx++) { html + img classsponsor-logo idsponsorId + idx + src alt>; } html + /div>; $(#sponsors).html(html); setSponsorVisibility(); tid setInterval(do_sponsors, 10000); }); } /* do_listing is the main worker method to generate the html for this page. * This will analyze the requested URL and either generate a listing * or redirect to a new URL that may work better. */ function do_listing() { console.log(href: %s, location.href); console.log(hostname: %s protocol: %s, location.hostname, location.protocol); console.log(pathname: %s, location.pathname); console.log(origin: %s, location.origin); console.log(orientation: %s agent: %s, window.orientation, navigator.userAgent); console.log(isMobileDevice: %s, isMobileDevice()); /* hack to force larger font size for blogger iFrames */ if (window.innerHeight 2400) { document.body.style.fontSize 20px; } else { document.body.style.fontSize isMobileDevice() ? 24px : 20px; } console.log(windows width: %d height: %d fontSize: %s, window.innerWidth, window.innerHeight, document.body.style.fontSize); /* Check for legacy ?path2018/lmcx2018 style href */ var path_rx (.*)?&path(^&+)(&.*)?$; var path_rx_match location.href.match(path_rx); /* if we do not have a slash at the end of the URL redirect to URL + / */ if (!path_rx_match && !location.pathname.endsWith(/)) { redir location.href + /; console.log(Redirction to URL + /: %s ***************, redir); location redir; return; } /* Top Level request, add the Google Search Engine scripting to the gcse div */ if ((!path_rx_match && location.pathname /) || location.pathname /index.html) { prefix generate_listing(prefix, /, false, true); get_quote(location.origin); //get_sponsors(location.href); $(#gcse).html(gcse:search>/gcse:search>); return; } /* Deprecated * If we have a path_rx_match on the ?pathtarget then we need to find the target * and redirect the browser to it. */ if (path_rx_match) { prefix path_rx_match2 + /; prefix prefix.replace(/^.\//, ); generate_listing(prefix, /, true, false); get_quote(location.origin); get_sponsors(location.href); return; } /* Proper URL, generate listing */ if (location.pathname.endsWith(/)) { /* get rid of prefix ./ */ prefix location.pathname; prefix prefix.replace(/^\//, ); generate_listing(prefix, /, false, false); get_quote(location.origin); get_sponsors(location.href); return; } /* Cannot find anything reasonable, redirect */ //location location.hostname; } /* ------------------------------- */ function loaded () { function search() { var cx 013477625228922489518:od4vbi02-og; var gcse document.createElement(script); gcse.type text/javascript; gcse.async true; gcse.src https://cse.google.com/cse.js?cx + cx; var s document.getElementsByTagName(script)0; s.parentNode.insertBefore(gcse, s); }; search(); /* This is the main worker method to generate the html to display */ do_listing(); /* * Activate tooltips */ $(data-toggletooltip).tooltip({ container: body }); } console.log(script hello);/script>/body>/html>
Port 443
HTTP/1.1 200 OKContent-Type: text/htmlContent-Length: 60230Connection: keep-aliveDate: Tue, 17 Feb 2026 11:10:57 GMTLast-Modified: Thu, 25 Sep 2025 03:18:14 GMTETag: 83cae220fd7e7f018050b6ca1fce5014x-amz-server-side-encryption: AES256x-amz-meta-s3cmd-attrs: atime:1758750387/ctime:1758770277/gid:1000/gname:sl/md5:83cae220fd7e7f018050b6ca1fce5014/mode:33204/mtime:1758770277/uid:1000/uname:slx-amz-version-id: b_VPmPat1F_iDX12Ve72wTih8aS.vmDfAccept-Ranges: bytesServer: AmazonS3X-Cache: Error from cloudfrontVia: 1.1 4b800f7fa2c3fbb9f4f3c505b0df315e.cloudfront.net (CloudFront)X-Amz-Cf-Pop: HIO52-P1X-Amz-Cf-Id: hQXwNkc_1emDM2iY7OC-pBGcALp0VyT-HdUzt3z6TA-6Lin1UD0GZQ !DOCTYPE html>!-- vim: syntaxjavascript -->!--S3 Bucket Static Website index.html to display RaceResult filesCopyright(c) 2018-2025 Stuart.Lynne@gmail.comgit@bitbucket.org:stuartlynne/raceresults.git N.B. This index.html file assumes the following: - used on an Amazon S3 bucket configured as a static webserver - the S3 static webserver index and error documents both point at index.htmlN.B. HTTPS - CloudFront is used to provide HTTPS access - files have cache times set on upload - CloudFront is configured for compressingLast modified: Thu Jun 26 06:00:43 PM PDT 2025 101-->html>head> title>RaceResults/title>!-- Google Analytics -->script async srchttps://www.googletagmanager.com/gtag/js?idUA-64955454-2>/script>script> window.dataLayer window.dataLayer || ; function gtag(){dataLayer.push(arguments);} gtag(config, UA-64955454-2); gtag(event, page_view, { send_to: UA-64955454-2 });gtag(js, new Date()); /script>!-- Google Custom Search Engine This supports the use of a Google Custom Search engine. For this to work well it is important that Google robots like our site. To ensure that we need to do twothings: 1. periodically verify that GSE can run this page, /index.html and not have an issues with the Javascript. See the fetchmd() function for comments on one issue encountered 2019-04. 2. periodically generate and upload a valid robots.txt file.C.f.https://stackoverflow.com/questions/54408012/google-search-console-error-uncaught-syntaxerror-unexpected-token-functionTo test this use the Google Search Console: https://search.google.com/search-console?resource_idsc-domain%3Aresults.wimsey.co&hlen-GBGo to URL Inspection, enter results.wimsey.co/index.html and then test live url.-->!--script> (function() { var cx 013477625228922489518:od4vbi02-og; var gcse document.createElement(script); gcse.type text/javascript; gcse.async true; gcse.src https://cse.google.com/cse.js?cx + cx; var s document.getElementsByTagName(script)0; s.parentNode.insertBefore(gcse, s); })();/script>!-- load javascript dependencies, jquery, marked and bootstrap -->script defer srchttps://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js>/script>script defer srchttps://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js>/script>script defer srchttps://cdn.jsdelivr.net/npm/marked/marked.min.js>/script>!-- favicon -->meta charsetutf-8>meta nameviewport contentwidthdevice-width, initial-scale1, maximum-scale1, user-scalable0>link relapple-touch-icon sizes57x57 href/icons/apple-icon-57x57.png>link relapple-touch-icon sizes60x60 href/icons/apple-icon-60x60.png>link relapple-touch-icon sizes72x72 href/icons/apple-icon-72x72.png>link relapple-touch-icon sizes76x76 href/icons/apple-icon-76x76.png>link relapple-touch-icon sizes114x114 href/icons/apple-icon-114x114.png>link relapple-touch-icon sizes120x120 href/icons/apple-icon-120x120.png>link relapple-touch-icon sizes144x144 href/icons/apple-icon-144x144.png>link relapple-touch-icon sizes152x152 href/icons/apple-icon-152x152.png>link relapple-touch-icon sizes180x180 href/icons/apple-icon-180x180.png>link relicon typeimage/png sizes192x192 href/icons/android-icon-192x192.png>link relicon typeimage/png sizes32x32 href/icons/favicon-32x32.png>link relicon typeimage/png sizes96x96 href/icons/favicon-96x96.png>link relicon typeimage/png sizes16x16 href/icons/favicon-16x16.png>link relmanifest href/icons/manifest.json>!-- link relstylesheet href/css/results.css >-->meta namemsapplication-TileColor content#ffffff>meta namemsapplication-TileImage content/ms-icon-144x144.png>meta nametheme-color content#ffffff>/head>body onload loaded() >!-- BUCKET_URL - S3 Configuration EXCLUDE_FILE - list of files not to display EXCLUDE_DIR - list of dirs not to display EXCLUDE_EXT - list of extensions to not display-->script typetext/javascript> var BUCKET_URL https://wimseyraceresults.s3-us-west-2.amazonaws.com; // XXX testing // index.html can be uploaded to s3://wimseytestresults: // http://wimseytestresults.s3-website-us-west-2.amazonaws.com // // It will still access the files from https://wimseyracereults etc. // This URL can be used to test with non-live data. //var BUCKET_URL https://wimseytestresults.s3-us-west-2.amazonaws.com; /* exclude the following files, directories and files with specified extensions */ var EXCLUDE_FILE Makefile, index.html,test.html,list.js, qrcode.html, md.html, Title.md, Credits.md, robots.txt, sitemap.txt, sitemap.xml, sponsors.json, quotes.json ; var EXCLUDE_PREFIX photos-; var EXCLUDE_DIR javascript, icons, photos, css; var EXCLUDE_EXT css, jpg, cgi, png, xml;/script>!-- Define the divs that will receive the HTML output to display -->div idbackListing> /div>div idtitle stylemargin-top: 40px> /div>div idgcse> /div>div iddirsListing> /div>div idtopRegistrationListing> /div>div idcommuniqueListing> /div>div ideventListing> /div>div idpressListing> /div>div idseriesListing> /div>div idstartListing> /div>div idothersListing> /div>div iddocsListing> /div>div idregistrationListing> /div>div idcredits> /div>div idsponsors> /div>div idquote> /div>!-- Makes the list line size slightly larger -->style typetext/css mediascreen> li{ margin: 10px 0; } /style>!-- Make the font size responsive -->style typetext/css mediascreen> @charset utf-8; /* CSS Document */ /* Smartphones (portrait and landscape) ----------- */ //@media only screen { body {color: red; background-color: lightblue; font-size: 700%} } /* Desktop ---------------------------------------- */ //@media only screen and (min-device-width : 992px) { body { font-size: 120%; background-color: gold; } } @media only screen and (min-device-width : 992px) { body { font-size: 120%; background-color: white; } } /* start of large tablet styles */ //@media screen and (max-width: 991px) { body { font-size: 150%; background-color: lightblue; } } @media screen and (max-width: 991px) { body { font-size: 150%; background-color: white; } } /* start of medium tablet styles */ //@media screen and (max-width: 767px) { body { font-size: 125%; background-color: lightgreen; } } @media screen and (max-width: 767px) { body { font-size: 125%; background-color: white; } } /* Desktop Blogger iFrame ------------------------- */ //@media only screen and (device-height : 2400px) { body { font-size: 120%; background-color: yellow; } } @media only screen and (device-height : 2400px) { body { font-size: 120%; background-color: white; } } /* start of phone styles */ //@media screen and (max-width: 479px) { body { font-size: 120%; background-color: lightsalmon; } } @media screen and (max-width: 479px) { body { font-size: 120%; background-color: white; } } /* CSS Document */ body { background: #fff; color: #444; font-family: Open Sans, sans-serif; font-size: 16px; font-weight: lighter; margin-bottom: 1em; } /* CSS for table */ .subtable { width: 100%; border-collapse: collapse; margin: 5px 0; } .subtable td { padding: 4px 8px; border-bottom: 1px solid #ddd; font-size: 90%; } /* CSS for buttons */ .xmobile-button { vertical-align: top; border: none; /* Remove border */ background-color: lightgrey; border-radius: 2px; margin: 2px 2px; height: 24px; font-size: 80%; padding: 2px 6px; cursor: pointer; border: 1px solid #ccc; /* Add a border */ } .xmobile-button { height: 40px; font-size: 90%; } .desktop-button:hover { background-color: #ddd; } .mobile-button { vertical-align: top; background-color: lightgrey; border-radius: 2px; margin: 2px 2px; height: 40px; font-size: 80%; } .desktop-button { vertical-align: top; border-radius: 2px; margin: 2px 2px; height: 24px; font-size: 60%; } /* Hover */ a.hover {text-decoration: none; color: black;} //a.hover:hover {text-decoration: underline;} /* ———————————————————————————————————————————————— Sponsors ———————————————————————————————————————————————— */ .sponsor-container { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 0.75em; margin: 0.5em 0 0.75em; text-align: center; } .sponsor-logo { flex: 1 1 180px; max-width: 220px; max-height: 90px; width: auto; height: auto; object-fit: contain; transition: opacity 0.5s ease; } /* large desktop: allow a touch more height */ @media only screen and (min-width: 1200px) { .sponsor-logo { max-height: 110px; } } /* tablets & small desktops */ @media only screen and (max-width: 991px) { .sponsor-logo { flex-basis: 160px; max-height: 80px; } } /* mobile portrait: show two logos, remove wrap stacking */ @media only screen and (max-width: 767px) and (orientation: portrait) { hr { margin: 0.25em 0; } .sponsor-container { gap: 0.5em; } .sponsor-logo { flex: 1 1 calc(50% - 0.5em); max-width: calc(50% - 0.5em); max-height: 68px; } } /* ————————————————————————————————————————— Sponsors-style Title Block ————————————————————————————————————————— */ #title { /* remove default margins/padding if any */ margin: 0.5em 0; padding: 0; } /* the actual clamped container */ .title-block { overflow: hidden; /* hide overflow */ position: relative; max-height: 100px; /* desktop default */ transition: max-height 0.3s ease; } /* A gentle “fade-out” gradient so users know there’s more above the clamp */ .title-block::after { content: ; position: absolute; bottom: 0; left: 0; right: 0; height: 3em; background: linear-gradient(to bottom, rgba(255,255,255,0), #fff); } /* tablet & small desktops: a little taller block */ @media only screen and (min-width: 768px) and (max-width: 991px) { .title-block { max-height: 50px; } } /* mobile portrait: shrink the block */ @media only screen and (max-width: 767px) and (orientation: portrait) { .title-block { max-height: 120px; } }/style>script > function isMobileDevice() { var isMobile navigator.userAgent.toLowerCase().match(/mobile/i); return isMobile ? true : false; }; function getext(filename) { if (typeof filename undefined) return ; var ext filename.split(.).pop(); return ext filename ? : ext; } function getbasename(filename) { return typeof filename undefined ? : filename.substring(0, filename.lastIndexOf(.)) || filename; } /* Race Results are displayed in a table, each race having two rows. * The first row is the name of the race and the second is normally invisible row * containing the buttons to click on to get race results or photos. * * The following are used to track which, if any, of the invisible row are * currently displayed. */ var visibleRow 0; var visibleTable ; var timeout 0; /* rowoff * Make a displayed row invisible again. */ function rowoff(tablename, id) { row document.getElementById(tablename).rowsid; if (!row) return; row.style.display none; if (visibleRow id && visibleTable tablename) { visibleRow 0; visibleTable ; } } /* rowoffdelayed * use setTimeout to call rowoff after timedelay */ function rowoffdelayed(tablename, id) { timeout setTimeout(function() {rowoff(tablename, id);}, 20000); } /* toggle * Make a button row visible. */ function toggle(tablename, id) { /* if there is a pending timeout to do a rowoff cancel it */ if (timeout) { clearTimeout(timeout); timeout 0; } /* Check if the currently visible row is or is not the same. * Do nothing (return) if the same, turn off any other. */ if (visibleTable ! ) { if (visibleRow id && visibleTable tablename) return; rowoff(visibleTable, visibleRow); } /* find and set display to to allow row to be displayed */ row document.getElementById(tablename).rowsid; if (!row) return; row.style.display row.style.display none ? : none; visibleRow id; visibleTable tablename; } function do_aref(ref, text) { return a href + ref + classhover targettop> + text + /a>; } function do_button(text, title) { var button isMobileDevice() ? mobile : desktop; //return button typebutton classbtn btn-secondary data-toggletooltip data-placementright title + title + > + text + /button>; return button typebutton class+button+-button data-toggletooltip data-placementright title + title + > + text + /button>; } function do_aref_with_button(ref, text, tooltip) { return do_aref(ref, do_button(text, tooltip)); } function old_do_file_button(ref, ext, lastModified) { lastModified lastModified.replace(/T/, ).replace(/\..*Z/, ); return do_aref_with_button(ref, ext.toUpperCase(), Uploaded: + lastModified); } function do_file_button(ref, ext, lastModified) { lastModified lastModified.replace(/T/, ).replace(/\..*Z/, ); let icon ; switch (ext.toLowerCase()) { case pdf: icon 📄; // Document break; case html: icon 🌐; // Web / browser break; case md: icon 📝; // Markdown note break; case gpx: icon 🗺️; // Map break; case xlsx: case xls: icon 📊; // Spreadsheet break; default: icon 📁; // Generic file } return do_aref_with_button(ref, `${icon} ${ext.toUpperCase()}`, Uploaded: + lastModified); } function do_qr_aref(text) { text text.replace(/^\//, ); ref location.protocol + // + location.hostname + location.pathname; return do_aref_with_button(location.origin + /qrcode.html?path + ref , text, QR Code for this page); } /* do_file_aref * Generate an aref for a file, this uses the last modified time for the tooltip */ function do_file_aref(ref, text, extension, lastModified) { //console.log(do_file_aref: ref: %s text: %s extension: %s, ref, text, extension); text text.replace(/^\//, ); lastModified lastModified.replace(/T/, ); lastModified lastModified.replace(/\..*Z/, ); /* check for markdown and do a button that uses md.html to render */ if (extension md) { ref ref.replace(/^\//, ); return do_aref_with_button(location.origin + /md.html?path + ref, text, ref + Uploaded: + lastModified); } /* check for photos- file, that gets translated back to path in the /photos- directory * for the button a ref. */ if (extension.startsWith(photos-)) { text text.replace(/photos-/, ); newref ref.replace(/\.photos-.*/, .html); newref newref.replace(/\//, ); newref newref.replace(/\//g, -); return do_aref_with_button(/ + extension + / + newref, text, ref + Uploaded: + lastModified); } /* normal file a ref, typically html, pdf, etc. */ return do_aref_with_button(ref, text, ref + Uploaded: + lastModified); } /* do_file_row * Generate a row for a file in a table, this is used to display the file name, extension, * last modified time and a link to the file. * * ref - the file reference * label - the file label, typically the basename * extension - the file extension * lastModified - the last modified time of the file */ function do_file_row(ref, label, extension, lastModified) { lastModified lastModified.replace(/T/, ).replace(/\..*Z/, ); const link do_aref(ref, View); return `tr>td>${label}/td>td>${extension.toUpperCase()}/td>td>${lastModified}/td>td>${link}/td>/tr>`; } /* fetchmd * Fetch a markdown file, render it, and return the rendered html. This is * used for Title.md and Credits.md. * * N.B. this is an asynchronous process that will eventually fill a specified the * specified div $(id).html(...) */ function fetchmd(mdfile, id, top, bottom) { mdfile location.protocol + // + location.hostname + / + mdfile; /* 2019-04-01 * N.B. response > response.text() syntax works OK for Chrome, but Google Search * engine indexing fails with: Uncaught SyntaxError: Unexpected token function. * E.g. * .then(response > response.text()) FAILS * .then(function (response) { return response.text(); }) OK * * C.f.https://stackoverflow.com/questions/54408012/google-search-console-error-uncaught-syntaxerror-unexpected-token-function * * To test this use the Google Search Console: * https://search.google.com/search-console?resource_idsc-domain%3Aresults.wimsey.co&hlen-GB * Go to URL Inspection, enter results.wimsey.co/index.html and then test live url. */ //console.log(fetchmd: %s, mdfile); fetch(mdfile) .then(function (response) { return response.text(); }) .then(function(data) { return $(# + id).html(top + marked.parse(data) + bottom); }); } function registrationLineIsActive(line, today) { var dateMatch line.match(/(\d{4}-\d{2}-\d{2})/); if (!dateMatch) return true; var eventDate new Date(dateMatch1 + T00:00:00); if (isNaN(eventDate.getTime())) return true; return eventDate.getTime() > today.getTime(); } function appendRegistrationLines(listElement, text) { if (typeof text ! string) return; var lines text.split(/\r?\n/) .map(function(line) { return line.trim(); }) .filter(function(line) { return line.length > 0; }) .filter(function(line) { return !/^#+\s*registration links$/i.test(line); }) .filter(function(line, idx) { return !(idx 0 && /^registration links$/i.test(line)); }); var today new Date(); today.setHours(0, 0, 0, 0); lines lines.filter(function(line) { return registrationLineIsActive(line, today); }); if (!lines.length) return 0; var markdown lines.map(function(line) { return /^-*+\s/.test(line) ? line : - + line; }).join(\n); var parsed $(div>).html(marked.parse(markdown)); var items parsed.find(li); if (!items.length) return 0; var appendedCount 0; items.each(function() { $(this).find(a).attr(target, _blank).attr(rel, noopener noreferrer); listElement.append($(this)); appendedCount + 1; }); return appendedCount; } function renderRegistrationLinks(files, targetId, heading) { var source Array.isArray(files) ? files.slice() : ; if (!source.length) return; source.sort(sortThings); var title heading || Registration Links; var container $(div classregistration-block>hr>h3> + title + /h3>/div>); var listElement $(ul classregistration-list>/ul>); container.append(listElement); $(targetId).empty().append(container); var pending source.length; var appended false; console.log(Registration render start, targetId, source.map(function(item) { return item.Key; })); source.forEach(function(item) { var fileUrl location.protocol + // + location.hostname + / + item.Key; fetch(fileUrl) .then(function(response) { return response.text(); }) .then(function(text) { var added appendRegistrationLines(listElement, text); console.log(Registration file processed, item.Key, added, added); if (listElement.children().length) appended true; }) .catch(function(error) { console.log(registration fetch failed: %s - %o, item.Key, error); }) .then(function() { pending - 1; if (pending 0 && !appended) { console.log(Registration render complete; removing empty container for, targetId); container.remove(); } if (pending 0 && appended) { console.log(Registration render complete with entries for, targetId); } }); }); } /* generate_dir_list * * Produce an ordered list of arefs to relative directories. E.g.: * * prefix results ref * 2018/ href2018/ * 2018/ 2018/ev2018/ hrefev2018/ * * The names hash contains names to use instead of the short series tag. */ function generate_dir_list(prefix, results, id, names, topflag) { var ol_body p>ol>; for (i 0; i results.length; i++) { dirname resultsi; dirname dirname.replace(prefix, ); dirname dirname.replace(/\/$/, ); try { if(typeof dirname ! undefined) { }} catch(e) { console.log(dirname is undefined); } text (dirname in names) ? namesdirname : dirname; ol_body + topflag ? do_aref_with_button(dirname + /, text, ) : li> + do_aref(dirname + /, text) + /li>; } ol_body + /ol>; $(id).html(ol_body); } function commonPrefix(strings) { if (strings.length 0) return ; // Start with the first string as the prefix candidate let prefix strings0; console.log(strings) console.log(prefix: %s, prefix) // Iterate through the strings starting from the second one for (let i 1; i strings.length; i++) { // Compare characters of each string with the current prefix for (let j 0; j prefix.length; j++) { // If a mismatch is found, update the prefix if (stringsij ! prefixj) { prefix prefix.slice(0, j); break; } } // If the current string is shorter than the prefix, update the prefix if (stringsi.length prefix.length) { prefix prefix.slice(0, stringsi.length); } console.log(prefix%d: %s, i, prefix) // If the prefix becomes empty, theres no common prefix if (prefix ) break; } console.log(prefix: %s, prefix) return prefix; } function normalizeEventKey(basename) { // Remove r1-, r2- suffixes basename basename.replace(/-r\d-/, ); // Match full pattern up to event name (e.g. 2025-03-22-Murchie-100PM) let match basename.match(/^(\d{4}-\d{2}-\d{2})-(^-+)/); if (match) { return match1 + - + match2; // e.g. 2025-03-22-Murchie } return basename; } function generate_file_list(section, prefix, files, id, tablename, top) { if (files.length 0) return; files.sort(sortThings); const groupMap {}; // 2025-03-22-Murchie → list of { key, ext, file } for (const file of files) { const ext getext(file.Key); let basename getbasename(file.Key).replace(prefix, ); if (basename Title && ext md) { //fetchmd(file.Key, title, , hr>); fetchmd( file.Key, title, div classtitle-block>, // top wrapper /div>hr> // close + divider ); continue; } if (basename Credits && ext md) { fetchmd(file.Key, credits, hr>, ); continue; } if (EXCLUDE_EXT.includes(ext)) continue; if (EXCLUDE_FILE.includes(basename) || EXCLUDE_FILE.includes(`${basename}.${ext}`)) continue; // Remove trailing -r1-, -r2- etc. for grouping const cleanBase basename.replace(/-r\d-/, ); const groupKey normalizeEventKey(cleanBase); if (!groupMapgroupKey) groupMapgroupKey ; groupMapgroupKey.push({ basename: cleanBase, ext: ext, key: file.Key, lastModified: file.LastModified }); } let html `${top}h3>${section}/h3>table id${tablename}>`; let rowId 1; let fid 1; for (const groupKey, entries of Object.entries(groupMap)) { const onmouseover `toggle(${tablename},${rowId})`; const onmouseleave `rowoffdelayed(${tablename},${rowId})`; // Display group entry const label groupKey.replace(/-/g, ).replace(/(\d{4}) (\d{2}) (\d{2})/, $1-$2-$3); html + `tr>td>em>${fid++}. /em>a href# classhover onmouseleave${onmouseleave} onmouseover${onmouseover}>${label}/a>/td>/tr>`; // Build subtable for this group let subtable table classsubtable>; const subgroups {}; for (const e of entries) { // Group by full filename to get 100PM vs 1030AM etc. let shortName e.basename.replace(groupKey + -, ); shortName shortName.replace(/-_/g, ).replace(/r\d-/, ); if (!subgroupsshortName) subgroupsshortName ; subgroupsshortName.push(e); } //for (const name, versions of Object.entries(subgroups)) { // subtable + `tr>td>${name}/td>td>`; // for (const v of versions) { // subtable + do_file_button(/ + v.key, v.ext, v.lastModified) + ; // } // subtable + /td>/tr>; //} const subgroupKeys Object.keys(subgroups); /* build subtable * If there is only one row, just show the icons, otherwise show name + icons. */ if (subgroupKeys.length 1) { // Only one row — omit name, show just icons const versions subgroupssubgroupKeys0; subtable + `tr>td colspan2>`; for (const v of versions) { subtable + do_file_button(/ + v.key, v.ext, v.lastModified) + ; } subtable + `/td>/tr>`; } else { // Multiple rows — show name + icons for (const name, versions of Object.entries(subgroups)) { subtable + `tr>td>${name}/td>td>`; for (const v of versions) { subtable + do_file_button(/ + v.key, v.ext, v.lastModified) + ; } subtable + /td>/tr>; } } subtable + /table>; html + `tr styledisplay:none>td colspan2>${subtable}/td>/tr>`; rowId + 2; } html + /table>; $(id).html(html); } /* button_generate_file_list * * Produce an ordered list of arefs to files. E.g.: * * prefix results ref * 2018/a.html a html hrefa.html */ function button_generate_file_list(section, prefix, files, id, tablename, top) { console.log(generate_file_list: Tablename: %s prefix: %s Files: %s, tablename, prefix, files); if (files.length 0) return ; var ol_body top + h3> + section + /h3>table id + tablename + >; var filenames ; var basenames new Map() /* last_date_rx for (var i 0; i files.length; i++) { basename getbasename(filesi.Key); basename basename.replace(prefix, ); console.log(xfiles%d: %s, i, basename); date_rx /^\d\d\d\d \d\d \d\d /; date_rx_match basename.match(date_rx); if date_rx_match ! && date_rx_match ! last_date_rx { //if last_date_rx ! { //} last_date_rx date_rx_match; filenames.push(basename); } } common_prefix commonPrefix(filenames); console.log(common_prefix: %s, common_prefix); */ /* Generate list entries, this is slightly complicated to support generating a single * list entry for multiple files that differ only in the file extension, with separate * arefs for each extension. */ var fid 1; var trid 1; var pdfs 0; var htmls 0; var first true; var count 0; var curfile ; var curdate ; for (var i 0; i files.length; i++) { console.log(files%d: %s, i, filesi.Key); /* get file extension and basename */ extension getext(filesi.Key); basename getbasename(filesi.Key); basename basename.replace(prefix, ); count++; console.log(files%d: \%s\ \%s\, i, basename, extension); console.log(---); console.log(%s:%d %s %s, section, i, basename, extension); console.log(%s:%d %s %s PHOTO: %d, section, i, basename, extension, extension.search(/photos/)); console.log(%s:%d %s %s PHOTO: %d, section, i, basename, extension, extension.indexOf(/photos/)); /* Check for Title.md and Credits.md files, render markdown in title and credit area. * N.b. These initiate asynchronous file requests to fill a specific div with data * rendered from markdown to html. */ if (basename Title && extension md) { fetchmd(filesi.Key, title, , hr>); continue; } if (basename Credits && extension md) { fetchmd(filesi.Key, credits, hr>, ); continue; } /* exclude files */ try { if(typeof EXCLUDE_EXT ! undefined) { if (EXCLUDE_EXT.includes(extension)) {continue;} } } catch(e) { console.log(EXCLUDE_EXT is undefined); } try { if(typeof extension ! undefined) { if (extension.includes(EXCLUDE_EXT)) {continue;} }} catch(e) { console.log(extension is undefined); } try { if(typeof EXCLUDE_FILE ! undefined) { if (EXCLUDE_FILE.includes(basename)) {continue;} if (EXCLUDE_FILE.includes(basename + . + extension)) {continue;} }} catch(e) { console.log(EXCLUDE_FILE is undefined); } /* get rid of everything after last dash e.g. Squamish-Elite becomes Squamish */ if (extension.search(/photos/) 0) { /* Dont display annoying CrossMgr -r1-. labels at the end of file names, * only the display name is modified. The file name is left so that the aref * will work correctly. */ basename basename.replace(/r\d-/, ); console.log(%s:%d %s %s A, section, i, basename, curdate); basename basename.replace(/-^-*$/,); console.log(%s:%d %s %s B, section, i, basename, curdate); /* if the name ends in whitespace and a number, remove, eg. WTNC UBC 730 */ basename basename.replace(/ -\d+$/, ); console.log(%s:%d %s %s C, section, i, basename, curdate); //dashes (basename.search(-)); dashes (basename.match(/-/g)); if (dashes ! null) { console.log(%s:%d dashes: %d, section, i, dashes); if (dashes > 3) { basename basename.replace(/-^-*$/, ); } } } else { console.log(%s:%d %s %s PHOTO, section, i, basename, curdate); } //console.log(basename: %s extension: %s curfile: %s, basename, extension, curfile); //console.log(basename: %s extension: %s curdate: %s, basename, extension, curdate); console.log(%s:%d %s %s C, section, i, basename, curdate); displayname basename; basename basename.replace(/-/g, ); displayname displayname.replace(/(....)-(..)-(..)-/, $1%$2%$3 ); displayname displayname.replace(/-/g, ); displayname displayname.replace(/_/g, ); displayname displayname.replace(/\+/g, ); displayname displayname.replace(/(....)%(..)%(..) /, $1-$2-$3 ); date_rx /^\d\d\d\d \d\d \d\d /; date_rx_match basename.match(date_rx); if (date_rx_match) { datename date_rx_match0; //console.log(generate_file_list%d: \%s\ date: %s, i, basename, datename); } else { datename ; } /* List entries for files are grouped, so that multiple files that differ only in the suffix are * displayed in a single entry, with multiple buttons for the different styles, e.g. html, pdf */ //if (curfile ! && (curfile.startsWith(basename) || basename.startsWith(curfile))) if (curdate ! && curdate datename) { console.log(MATCH%d: %s, i, curfile); console.log(MATCH%d: %s, i, basename); /* we may have multiple pdf and html files, so well number them */ if (extension pdf) extension + + ++pdfs; if (extension html) extension + + ++htmls; ol_body + do_file_aref(/ + filesi.Key, extension, extension, filesi.LastModified); continue; } //console.log(NOMATCH: %s, curfile); //console.log(NOMATCH: %s, basename); curfile basename; curdate datename; pdfs 0; htmls 0; /* we may have multiple pdf and html files, so well number them */ if (extension pdf) extension + + ++pdfs; if (extension html) extension + + ++htmls; if (!first) { ol_body + /td>/tr>; } if (extension ) { ol_body + tr>td>em> + fid + . /em> + do_file_aref(/ + filesi.Key, displayname, extension, filesi.LastModified) + /td>/tr>tr>td>; } else { var onmouseover \toggle(+tablename+,+trid+,false)\; var onmouseleave \rowoffdelayed(+tablename+,+trid+)\; ol_body + tr>td>em> + fid + . /em>; ol_body + a href# classhover onmouseleave+onmouseleave+ onmouseover+onmouseover+> + displayname + /a>; ol_body + /td>/tr>tr styledisplay:none>td>; ol_body + do_file_aref(/ + filesi.Key, extension, extension, filesi.LastModified); } first false; fid + 1; trid + 2; continue; } /* only display if we have found files */ if (!count) return; /* finish table */ ol_body + !first ? /td>/tr> : ; ol_body + /table>; $(id).html(ol_body); } /* getInfoFromS3Data * * This is given a xml file containing directory listing directly from the S3 bucket. * The xml file will be decomposed into a list of files and a list of directories. */ function getInfoFromS3Data(xml) { /* get files */ var files $.map(xml.find(Contents), function(item) { return { Key: $(item).find(Key).text(), LastModified: $(item).find(LastModified).text(), Type: file } } ); /* get directories */ var directories $.map(xml.find(CommonPrefixes), function(item) { return { Key: $(item).find(Prefix).text(), LastModified: , Size: 0, Type: directory } } ); var nextMarker ($(xml.find(IsTruncated)0).text() true) ? nextMarker $(xml.find(NextMarker)0).text() : null; return { files: files, directories: directories, prefix: $(xml.find(Prefix)0).text(), nextMarker: encodeURIComponent(nextMarker) } } function dotfilecheck(regex, key, include_array) { if (!key.match(regex)) return; key key.replace(regex, ); include_array.push(key); } function dotfilecheck2(regex, key, include_array) { if (!key.match(regex)) return; include_array.push(key); } //function strincludearray(string, matcharray) { // for (i 0; i matcharray.length; i++) { // if (string.includes(matcharrayi)) // return true; // } // return false; //} /* sortThings * a and b are hashes with a component called Key to compare on. */ function sortThings(a, b) { a1 a.Key.replace(/-/g, ); b1 b.Key.replace(/-/g, ); if (a1 > b1) return 1; else if (a1 b1) return -1; else if (a1 b1) return 0; } /* */ /* generate_listing * * Called to generate a listing for a specific directory. * N.B. the top level directory is reverse sorted to get most recent years at the top. * * Given the requested URL we will query the S3 bucket to get the contents of that directory. * */ function generate_listing(prefix, redirect, resultsonlyflag, topflag) { console.log(generate_listing: --------------------------) patharray prefix.split(/); redirect location.protocol + // + location.hostname + /; back ; cwd ; if (patharray.length > 2) { for (i 0; i patharray.length - 2; i++) { redirect + patharrayi + /; back + patharrayi; cwd patharrayi+1; } } else if (patharray.length > 1) { redirect /; back top; cwd patharray0; } var queryURL BUCKET_URL + /?delimiter/&prefix + prefix; console.log(queryURL: %s, queryURL); var sortReverse true; /* get queryURL initiates a connection to get the file and then returns. The * .done(...) function receives the data asynchronously. The data is processed * as received and to build up files and dirs lists. Those are then used to build * up htmn to put into the various divs. The browser will display these as * they are added to the divs. */ $.get(queryURL) .done(function(data) { var xml $(data); var communique_include ; var press_include ; var series_include ; var start_include ; var others_include ; var docs_include ; var registration_include ; var dirs_list ; var files_list ; var communique_list ; var press_list ; var series_list ; var start_list ; var others_list ; var docs_list ; var registration_list ; var files_exclude ; var dirs_exclude ; var names_exclude ; var info getInfoFromS3Data(xml); console.log(info.files: %s, info.files) /* build series and other include lists */ jQuery.each(info.files, function(idx, item) { console.log(get%d: file: %s, idx, item.Key); dotfilecheck(/^.*\/.COMMUNIQUE-/, item.Key, communique_include); dotfilecheck(/^.*\/.PRESS-/, item.Key, press_include); dotfilecheck(/^.*\/.SERIES-/, item.Key, series_include); dotfilecheck(/^.*\/.START-/, item.Key, start_include); dotfilecheck(/^.*\/.OTHER-/, item.Key, others_include); dotfilecheck(/^.*\/.DOC-/, item.Key, docs_include); dotfilecheck(/^.*\.REGISTRATION-/, item.Key, registration_include); dotfilecheck(/^.*\/.EXCLUDE-/, item.Key, files_exclude); dotfilecheck2(/^.*\/\..*-/, item.Key, names_exclude); dotfilecheck(/^.*\/.EXCLUDEDIR-/, item.Key, dirs_exclude); }); console.log(series_include %s, series_include) function getFileName(path) { return path.split(/).pop(); // Extracts only the last part of the object key } function strincludearray(string, matcharray) { for (let i 0; i matcharray.length; i++) { if (string.includes(matcharrayi)) { return true; } } return false; } jQuery.each(info.files, function(idx, item) { console.log(jQuery.each: %s, item.Key); // Skip hidden files inside directories if (item.Key.match(/\/\.^\/*$/)) { return; } console.log(jQuery.match), console.dir(item.Key); let fileName getFileName(item.Key); // Extracts the filename // Apply inclusion lists but only match the filename if (strincludearray(fileName, communique_include)) { communique_list.push(item); return; } if (strincludearray(fileName, press_include)) { press_list.push(item); return; } if (strincludearray(fileName, series_include)) { series_list.push(item); return; } if (strincludearray(fileName, start_include)) { start_list.push(item); return; } if (strincludearray(fileName, others_include)) { others_list.push(item); return; } if (strincludearray(fileName, docs_include)) { docs_list.push(item); return; } if (strincludearray(fileName, registration_include)) { registration_list.push(item); return; } // Exclusions should be checked before adding to any list if (strincludearray(fileName, files_exclude)) { return; } if (strincludearray(fileName, names_exclude)) { return; } // Handle special cases (Title.md, Credits.md, Sponsors.json) if (fileName Title.md) { fetchmd(item.Key, title, , hr>); return; } if (fileName Credits.md) { console.log(jQuery.each: fetchmd %s, item.Key); fetchmd(item.Key, credits, hr>, ); return; } if (fileName sponsors.json) { get_sponsors(item.Key); return; } let extension getext(item.Key); // Special case for photos files if (fileName.startsWith(photos-)) { console.log(info.files: %s matched photos, item.Key); return; } files_list.push(item); }); console.log(communique_list); console.dir(communique_list); console.log(press_list); console.dir(press_list); console.log(series_list); console.dir(series_list); console.log(start_list); console.dir(start_list); console.log(others_list); console.dir(others_list); console.log(docs_list); console.dir(docs_list); console.log(docs_include); console.dir(docs_include); console.log(files_list); console.dir(files_list); /* build up names hash */ names {}; for (i 0; i names_exclude.length; i++) { nameinfo names_excludei.split(-); tag nameinfo0.replace(/.*\/\./, ); namestag nameinfo1.replace(/_/g, ); } jQuery.each(info.directories, function(idx, item) { if (strincludearray(item.Key, dirs_exclude)) { return; } if (strincludearray(item.Key, EXCLUDE_DIR)) { return; } dirs_list.push(item.Key.replace(/\/$/, )); }); /* If the requested directory is empty or non-existent redirect upwards */ if (dirs_list.length 0 && files_list.length 0 && docs_list 0 && others_list 0 && communique_list 0 && press_list 0 && series_list 0 && start_list 0 ) { console.log(Empty redirection up: %s ***************, redirect); location redirect; } if (sortReverse) dirs_list dirs_list.reverse(); console.log(files list reverse sort); files_list.sort(sortThings); //files_list.reverse(); generate_dir_list(prefix, dirs_list, #dirsListing, names, topflag); if (topflag) { renderRegistrationLinks(registration_list, #topRegistrationListing, Upcoming Event Registrations); //generate_photo_list(prefix, dirs_list, #photosListing, names); } else { generate_file_list(Comminiques, prefix, communique_list, #communiqueListing, communiqueTable, hr>); generate_file_list(Press Releases, prefix, press_list, #pressListing, pressTable, hr>); generate_file_list(Series, prefix, series_list, #seriesListing, seriesTable, hr>); generate_file_list(Events, prefix, files_list, #eventListing, filesTable, p>); generate_file_list(Start Lists, prefix, start_list, #startListing, startTable, p>); generate_file_list(Other Results, prefix, others_list, #othersListing, otherTable, p>); generate_file_list(Documents, prefix, docs_list, #docsListing, docsTable, hr>); renderRegistrationLinks(registration_list, #registrationListing, Registration Links); } }) .fail(function(error) { console.log(generate_listing: get fail); console.log(generate_listing: data); console.error(error); // XXX // window.location.pathname redirect; }); var ol_body ; if (prefix ! && !resultsonlyflag) { ol_body + h2> + do_aref_with_button(redirect, back + /, ) + do_qr_aref(cwd) + /h2>; $(#backListing).html(ol_body); sortReverse false; } } /* fetchphotoinfo * Example of using promise to delay fetch */ /* async function fetchphotoinfo(photourl) { const response await fetch(photourl); const data await response.text(); return data; } */ /* start_listing * Example of using promise to delay fetch */ /* function start_listing(prefix, redirect, resultsonlyflag, topflag) { var photoURL BUCKET_URL + /?delimeter/&prefixphotos/; fetchphotoinfo(photoURL) .then(data > generate_listing(prefix, redirect, resultsonlyflag, topflag, data)); } */ /* get_quote */ function get_quote(origin) { var quotefile origin + / + quotes.json; $.ajaxSetup({cache:true}); $.getJSON(quotefile, function(data){ quotes ; $.each(data, function (index, value) { quotes.push(value); }); var random Math.floor(Math.random() * quotes.length); $(#quote).html(hr> + quotesrandom); }); } /* Sponsors support */ var tid; var sponsors ; var sponsor 0; var cursponsors , , , ; /* do_sponsor * Fade in a sponsor logo. */ function maxSponsorSlots() { var portraitMobile window.matchMedia((max-width: 767px) and (orientation: portrait)).matches; return portraitMobile ? 2 : 4; } function setSponsorVisibility() { var max maxSponsorSlots(); if (!sponsors.length) return; for (var i 0; i 4; i++) { var slot $(#sponsorId + i); if (!slot.length) continue; if (i max) { slot.css(display, ); if (!slot.attr(src)) { do_sponsor(i, false, true); } } else { slot.stop(true, true).css(display, none).attr(src, ); cursponsorsi ; } } sponsor Math.min(sponsor, max - 1); } function handleSponsorLayoutChange() { setSponsorVisibility(); } window.addEventListener(resize, handleSponsorLayoutChange); window.addEventListener(orientationchange, handleSponsorLayoutChange); function do_sponsor(slot, delay, fromVisibility) { if (!sponsors.length) return; var max maxSponsorSlots(); if (slot > max) { if (!fromVisibility) { $(#sponsorId + slot).attr(src, ); cursponsorsslot ; } return; } var id $(#sponsorId + slot); if (!id.length || !id.is(:visible)) { id.attr(src, ); cursponsorsslot ; return; } var active cursponsors.slice(0, max); var value; var guard 0; do { var random Math.floor(Math.random() * sponsors.length); value sponsorsrandom; guard++; if (guard > sponsors.length * 2) break; } while (active.indexOf(value.Sponsor) > 0); cursponsorsslot value.Sponsor; id.css(min-height, id.height()); id.fadeOut(delay ? 5000 : 0, function(){ id.attr(src, value.URL); id.attr(alt, value.Sponsor); id.fadeIn(delay ? 5000 : 0); }); } /* do_sponsors * Called from interval timer to load a new logo */ function do_sponsors() { var max maxSponsorSlots(); do_sponsor(sponsor); sponsor (sponsor + 1) % max; } /* get_sponsors * Called to load a sponsor json file. This will create the sponsors div * and set up interval timer to periodically change the displayed logos. */ function get_sponsors(sponsorfile) { var sponsorsfile location.origin + / + sponsorfile; //console.log(get_sponsors: %s, sponsorsfile); /* fetch the sponsor json file, unpack and start timer */ $.ajaxSetup({cache:true}); $.getJSON(sponsorsfile, function(data){ /* This is done after file data is available, * first iterate across array to build sponsors array. * N.B. we use weight to add extra entries to bias the * number of times a specific sponsor logo will be displayed. */ sponsors ; $.each(data, function (index, value) { for (i 0; i value.Weight; i++) { //console.log(get_sponsors%d:%s:%d %s, index, value.Weight, i, value.URL); sponsors.push(value); } }); /* set up the html, seed the logo slots, and start the interval timer */ let html hr>div classsponsor-container>; for (var idx 0; idx 4; idx++) { html + img classsponsor-logo idsponsorId + idx + src alt>; } html + /div>; $(#sponsors).html(html); setSponsorVisibility(); tid setInterval(do_sponsors, 10000); }); } /* do_listing is the main worker method to generate the html for this page. * This will analyze the requested URL and either generate a listing * or redirect to a new URL that may work better. */ function do_listing() { console.log(href: %s, location.href); console.log(hostname: %s protocol: %s, location.hostname, location.protocol); console.log(pathname: %s, location.pathname); console.log(origin: %s, location.origin); console.log(orientation: %s agent: %s, window.orientation, navigator.userAgent); console.log(isMobileDevice: %s, isMobileDevice()); /* hack to force larger font size for blogger iFrames */ if (window.innerHeight 2400) { document.body.style.fontSize 20px; } else { document.body.style.fontSize isMobileDevice() ? 24px : 20px; } console.log(windows width: %d height: %d fontSize: %s, window.innerWidth, window.innerHeight, document.body.style.fontSize); /* Check for legacy ?path2018/lmcx2018 style href */ var path_rx (.*)?&path(^&+)(&.*)?$; var path_rx_match location.href.match(path_rx); /* if we do not have a slash at the end of the URL redirect to URL + / */ if (!path_rx_match && !location.pathname.endsWith(/)) { redir location.href + /; console.log(Redirction to URL + /: %s ***************, redir); location redir; return; } /* Top Level request, add the Google Search Engine scripting to the gcse div */ if ((!path_rx_match && location.pathname /) || location.pathname /index.html) { prefix generate_listing(prefix, /, false, true); get_quote(location.origin); //get_sponsors(location.href); $(#gcse).html(gcse:search>/gcse:search>); return; } /* Deprecated * If we have a path_rx_match on the ?pathtarget then we need to find the target * and redirect the browser to it. */ if (path_rx_match) { prefix path_rx_match2 + /; prefix prefix.replace(/^.\//, ); generate_listing(prefix, /, true, false); get_quote(location.origin); get_sponsors(location.href); return; } /* Proper URL, generate listing */ if (location.pathname.endsWith(/)) { /* get rid of prefix ./ */ prefix location.pathname; prefix prefix.replace(/^\//, ); generate_listing(prefix, /, false, false); get_quote(location.origin); get_sponsors(location.href); return; } /* Cannot find anything reasonable, redirect */ //location location.hostname; } /* ------------------------------- */ function loaded () { function search() { var cx 013477625228922489518:od4vbi02-og; var gcse document.createElement(script); gcse.type text/javascript; gcse.async true; gcse.src https://cse.google.com/cse.js?cx + cx; var s document.getElementsByTagName(script)0; s.parentNode.insertBefore(gcse, s); }; search(); /* This is the main worker method to generate the html to display */ do_listing(); /* * Activate tooltips */ $(data-toggletooltip).tooltip({ container: body }); } console.log(script hello);/script>/body>/html>
View on OTX
|
View on ThreatMiner
Please enable JavaScript to view the
comments powered by Disqus.
Data with thanks to
AlienVault OTX
,
VirusTotal
,
Malwr
and
others
. [
Sitemap
]