38 Commits

Author SHA1 Message Date
Wyatt Allen
c601abccc3 Revert "Polygerrit now loads polymer-resin"
This reverts commit 0895052c01ac5ac657a9763d2ad9967d9ae55c18.

Reason for revert: issue 6387

Change-Id: I14e00addeab53606952aa3ea2d45a74eac7a9d8a
2017-06-02 09:37:37 -07:00
Mike Samuel
0895052c01 Polygerrit now loads polymer-resin
polymer-resin intercepts polymer property assignments
before they reach XSS-vulnerable sinks like `href="..."`
and text nodes in `<script>` elements.

This follows the instructions in WORKSPACE for adding a new bower
dependency with kaspern's tweak to use the dependency in a rule so
that it's found.  //lib/js/bower_components.bzl has already been
rolled-back per those instructions.

The license is the polymer license as can be seen at
https://github.com/Polymer/polymer-resin/blob/master/LICENSE though
I'm not sure that //tools/js/bower2bazel.py recognizes it as such.

Docs for the added component are available at
https://github.com/Polymer/polymer-resin/blob/master/README.md
https://github.com/Polymer/polymer-resin/blob/master/getting-started.md

With this change, when I introduce an XSS vulnerability as below,
polymer-resin intercepts and stops it.

Patch that introduces a strawman vulnerability.

--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
@@ -55,6 +55,10 @@
         url: '/q/status:abandoned',
         name: 'Abandoned',
       },
+      {
+        url: location.hash.replace(/^#/, '') || 'http://example.com/#fragment_echoed_here',
+        name: 'XSS Me',
+      },
     ],
   }];

---

Address kaspern's and paladox's comments.

---

Undo version bumps for bower dependencies.

---

Change Soy index template to parallel app/index.html.

---

update polymer-resin to version 1.1.1-beta

----

Load polymer-resin into polygerrit-ui/**/*_test.html

After this, I ran the tests with
  -l chrome
  -l firefox

I ran a handful of tests with -p and observed that the
console shows "initResin" is called before test cases start
executing.

These changes were done programmaticly by running the script below
(approximately) thus:
```
gerrit/ $ cd polygerrit-ui/app
app/ $ find . -name \*test.html | xargs perl hack-tests.pl
```

```
use strict;

sub removeResin($) {
  my $s = $_[0];
  $s =~ s@<link rel="import" href="[^"]*/polymer-resin/[^"]*"[^>]*>\n?@@;
  $s =~ s@<script src="[^"]*/polymer-resin/[^"]*"></script>\n?@@;
  $s =~ s@<script>\s*security\.polymer_resin.*?</script>\n?@@s;
  return $s;
}

for my $f (@ARGV) {
  next if $f =~ m@/bower_components/|/node_modules/@;

  system('git', 'checkout', $f);
  print "$f\n";

  my @lines = ();
  open(IN, "<$f") or die "$f: $!";
  my $maxLineOfMatch = 0;
  while (<IN>) {
    push(@lines, $_);
    # Put a marker after core loading directives.
    $maxLineOfMatch = scalar(@lines)
      if m@/webcomponentsjs/|/polymer[.]html\b|/browser[.]js@;
  }
  close(IN) or die "$f: $!";

  die "$f missing loading directives" unless $maxLineOfMatch;

  # Given ./a/b/c/my_test.html, $pathToRoot is "../../.."
  # assuming no non-leading . or .. components in the path from find.
  my $pathToRoot = $f;
  $pathToRoot =~ s@^\.\/@@;
  $pathToRoot =~ s@^(.*?/)?app/@@;
  $pathToRoot =~ s@\/[^\/]*$@@;
  $pathToRoot =~ s@[^/]+@..@g;

  my $nLines = scalar(@lines);
  open(OUT, ">$f") or die "$f: $!";

  # Output the lines up to the last polymer-resin dependency
  # loaded explicitly by this test.
  my $before = join '', @lines[0..($maxLineOfMatch - 1)];
  $before = removeResin($before);
  print OUT "$before";

  # Dump out the lines that load polymer-resin and configure it for
  # polygerrit.
  if (1) {
      print OUT qq'<link rel="import" href="$pathToRoot/bower_components/polymer-resin/standalone/polymer-resin-debug.html"/>
<script>
security.polymer_resin.install({allowedIdentifierPrefixes: [\'\']});
</script>
    ';
  }

  # Emit any remaining lines.
  my $after = join '', @lines[$maxLineOfMatch..$#lines];
  $after = removeResin($after);
  $after =~ s/^\n*//;
  print OUT "$after";

  close(OUT) or die "$f: $!";
}
```

---

update polymer-resin to version 1.2.1-beta

---

update Soy index template to new style polymer-resin initialization

----

fix lint warnings

----

Load test/common-test-setup.html into *_test.html

Instead of inserting instructions to load and initialize polymer-resin into
every test file, add a common-test-setup.html that does that and also fold
iron-test-helpers loading into it.

----

imported files do not need to load webcomponentsjs

Change-Id: I71221c36ed8a0fe7f8720c1064a2fcc9555bb8df
2017-05-30 23:16:09 -04:00
Kasper Nilsson
3a2f287d71 ES6ify /gr-diff-builder/*
Bug: Issue 6179
Change-Id: I9d72fb89a3c3ad916c9097bdf6e5fb31a1dd4cf0
2017-05-18 00:32:33 +00:00
Viktar Donich
29e1ce5e84 Collection of prospective test flake fixes
Potentially related:
https://github.com/Polymer/web-component-tester/issues/505

Bug: Issue 5792
Change-Id: I9ab6e8e40d9811dd52906335426764c052907609
2017-03-30 13:46:58 -07:00
Becky Siegel
cd2d4232f1 Reduce confusion between side and commentSide
Use '__isOnParent' as a boolean in place if 'side' ('PARENT vs
'REVISION').  In doing so, it's necessary to convert to/from 'side'
whenever interacting with the REST API.

Change-Id: Ic023c9be1969597e4b9c73a51cfed9f5eb9bc23e
2017-03-15 13:50:21 -07:00
Wyatt Allen
8fd847fb77 Initialize diff cursors as soon as diffs render content
Previously diff cursors initialized when their diffs completely finished
rendering. This means waiting longer than needed because the diff render
process includes processing and rendering syntax highlights whereas the
diff cursor only needs to wait on the content of the diff.

With this change diffs fire an event when they've finished with their
content only, and diff cursors await this event instead of the full
render.

Bug: Issue 5681
Change-Id: Ida9da83a20fe4c2dcd8920304504ec7e1185eb5d
2017-03-01 17:19:00 -08:00
Viktar Donich
c6894439c6 Take tab size into account for line-wrapping
Previously, the logic was only checking exact line wrap boundaries and
missed tabs advancing over the boundary.

Also, line breaks were added inside span elements wrapping tabs, which
prevented correct rendering.

Bug: Issue 5597
Change-Id: I750547cb574c02965d5a30ba57f791841779297a
2017-02-22 14:56:22 -08:00
Becky Siegel
a88f8c82d4 Fix corner case where storage gets duplicated for patchsets
Previously, there was an issue where if you create a draft comment in
side by side view and switch to unified view, the comment thinks it's in
the later patch set rather than the earlier one and a second copy gets
added to local storage with the later patchset as a component of the
key.

This was because the the thread group assumed all threads inside of it
had the same patch number. This change fixes that, so in the event that
a user switches from side by side to unified, the patch number will get
taken from the comment rather than the thread group.

Bug: Issue 5493
Change-Id: I7f00997bcb2e6f1001a5d58ac206acf5af3367d2
2017-02-10 14:52:57 -08:00
Becky Siegel
41bdc04cb8 Don't merge threads on same line left/right
Goes along with c/95273/. Adds commentSide attribute to comments to see
which side of the diff view they belong on. This is also used as part
of the locationRange for the gr-diff-comment-thread-group, so that two
thread groups can be on the same line or range for the unified group (
one for the right, one for the left).

Note: there is already a 'side' attribute on the gr-diff-comment, which
is confusing. This side actually references 'PARENT' or 'REVISION', to
identify whether the comment belongs to the parent or any revision. On
diffs where two revisions are compared to each other, this cannot be
used to determine left/right. However, because 'side' is part of
the CommentInfo entity[1], it is difficult to change the name and make
more sense out of that.

[1] https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#comment-info

Bug: Issue 5114
Change-Id: I5cc4c17d4bb134e31e5cc07ff9b08ed349488c97
2017-02-07 21:00:52 +00:00
Wyatt Allen
e153473446 Merge "Fix lint errors" 2017-02-02 01:45:00 +00:00
Wyatt Allen
455c91c88b Merge "Allow multiple threads per line, with different ranges" 2017-02-02 00:39:40 +00:00
Becky Siegel
562a30493e Allow multiple threads per line, with different ranges
- Add concept of diff comment thread groups, which are all of the
  threads at a particular line number.

- The thread group is responsible for breaking up comments into threads
  based on the range of the comment.

- Thread groups are ordered by the updated time of the first comment in
  the group.

- Thread groups are given a key, based on comment range, which is used
  to determine what thread group a new comment should go in (or if it
  needs a new one).

Feature: Issue 5292
Change-Id: If544e8bb879262de3ce5397e86124837b66ada04
2017-02-01 16:10:08 -08:00
Becky Siegel
90c8d2742a Fix lint errors
Change-Id: I455c266868738903400c031194fbfe22e5f41fdf
2017-02-01 14:55:24 -08:00
Wyatt Allen
3b824c577d Remove unnecessary HTML escaping
Because the `_computeAccountTitle` method was only used in a binding to
an HTML attribute, the manual HTML escaping performed in that method was
in not needed.

Since removing this, the only use-site for the `util.escapeHTML`
function is the `GrDiffBuilder`. To discourage general use of manual
HTML escaping (in favor of default escaping in Polymer bindings) -- as
well as to lighten util.js -- the escapeHTML function is moved into
`GrDiffBuilder`, its regex is made a constant, and is given a unit test.

Change-Id: I28c9f546cf50461e96995ecd6da8653e75554023
2017-01-30 17:28:57 -08:00
Wyatt Allen
1e3cd47707 Highlight add/remove diff lines more consistently
Diffs in PolyGerrit apply two shades of highlight to changed lines
(light and dark) to indicate the granularity of modifications and to
distinguish intraline edits. However, the logic for choosing the
background shade for diff lines would differ from that of GWT UI diffs
subtly.

 +----------------------------------+----------------------------------+
 |       GWT UI Shading Logic       |   PG Shading Logic (incorrect)   |
 +----------------------------------+----------------------------------+
 | Diff lines get a dark background | Diff lines get a dark background |
 | IFF they appear in a delta chunk | IFF they do NOT contain any      |
 | that is empty on the left OR     | intraline differences.           |
 | empty on the right.              |                                  |
 +----------------------------------+----------------------------------+
 |            Diff lines get a light background otherwise.             |
 +---------------------------------------------------------------------+

With this change, the shading logic in PolyGerrit is modified to match
that of the GWT UI.

Bug: Issue 4219
Bug: Issue 5117
Change-Id: Ice24292df777118c08c3e73f771720f8a186a183
2016-12-22 10:41:39 -08:00
Wyatt Allen
d970500d62 Annotate trailing whitespace per user preference
Add a simple annotation layer that marks trailing whitespace in diffs
(guarded by the `show_whitespace_errors` diff preference). The newly
supported diff preference is added to both diff preference controls. The
requirement that all annotation layers must implement `addListener` is
relaxed as the trailing whitespace layer is the third layer that doesn't
use it.

Adds tests for the layer and the diff preference.

Feature: Issue 4836
Change-Id: Ifba05216bf0bc3c0a8a094f5ef392b983091d59f
2016-11-20 13:31:30 -08:00
Becky Siegel
e7d19a9976 Add preference for line wrapping in diff preferences
Previously in Polygerrit, diff views were always displayed in the width
specified in diff preferences. This change gives the option to wrap
lines instead, which takes precedence over column width (the column
width option is hidden when line wrapping is selected), and fits the
diff view to screen.

The gerrit API already supports the 'lineWrapping' preference so this
change uses that already existing option.

Feature: Issue 4809
Change-Id: I0d9e292739b5910abfd04af63ec4c745bf06e446
2016-10-31 17:35:21 -07:00
Andrew Bonventre
fd434afe59 First round cleanup of network requests & errors from tests
+ These were slowing down tests in cases where it would actually hit a
  live server, potentially adding the latency from the network to the
  test.
+ Other fixes involve removing unused imports of util.js amongst other
  small tweaks/fixes.

Bug: Issue 4016
Change-Id: I442deefebeffc6a701e4922faccfe1c74b3a35b6
2016-10-16 06:56:37 +00:00
Wyatt Allen
1e94b4e472 Disable syntax highlighting files with very long lines
Syntax highlighting is generally non-blocking, but on a line-by-line
basis. Thus, when large, minified JS or CSS libraries (which are
generally few very long lines) are included in a change, the syntax
highlighter blocks rendering and the browser slows to a halt.

With this change, the diff builder will disable syntax highlighting when
any line of the (processed) diff exceeds 499 characters in length. This
is a stopgap measure in lieu of making syntax non-blocking within lines.

Bug: Issue 4750
Change-Id: I777c7fb64cf02e21e99db0f1cbca4badd162b3c0
2016-10-10 17:27:12 -07:00
Becky Siegel
55a1eec37d Fix jumping scroll position when diff view loads
Previously, there was an issue where if you started scrolling on a diff
view page before everything was loaded, the page would jump back to the
top after loading finished.

This change temporarily adjusts scroll behavior when scrolling is
detected mid-diff load. The scroll behavior is then restored after
loading completes.

Bug: Issue 4411
Change-Id: I8175d433632fd8710f1353f7ba5635b826151ce0
2016-10-06 07:57:41 -07:00
Wyatt Allen
be0142ca03 Improves visible tabs rendering
This change reverts visible tabs to use the » character. A few different
approaches have been used for rendering these tab indicators:

I:  Before the Annotation Pipeline, tab indicators were configured by a
    class that was optionally applied to tab elements by the diff
    builder. The class was guarded by the show_tabs preference and a CSS
    rule created a `::before` pseudo element to insert the character.
    (Thus also preventing the » from being copyable text.)

II: When the Annotation Pipeline was implemented, the ordering of layers
    called for intraline difference elements being rendered *inside* tab
    indicators. As a result, the » indicator would sometimes have a
    different background than the intraline difference, looking sloppy.

    To solve this, the pseudo element was positioned using absolute,
    allowing the pseudo element to consume no horizontal space and and
    the intraline background to extend across the entire tab.

III:When performance tuning, it was discovered that the
    absolute-positioned tab indicators were positioned incorrectly when
    GPU acceleration was hinted for the diff, so the containing tab
    elements were made relative.

IV: Continuing performance tuning, the tab indicators seemed to slow
    scrolling on very large diffs with tabs. It was mistakenly assumed
    (by me) that this was related to the pseudo-elements when it was
    actually caused by the thousands of positioning contexts they
    created using relative and absolute.

    Instead they were modified to use strike-through instead of a pseudo
    element, which was more-performant, but less-usable (it looked bad).

With this change, we roll-back the clock a little and solve a core
problem: namely that tab indicators were not annotated inside the
intraline differences. Fixing that, positioning tricks are no-longer
needed to make the background look right.

To do this, we decouple the tab elements (which control tab width) from
the tab indicators (which optionally make tabs visible). This is done
using an annotation layer that inserts tab indicator elements wherever
a tab character is used in the source content, and it does so after
intraline differences have been applied.

Bug: Issue 4441
Change-Id: I4fea2a3a20a7039bfeb746bd1e1c1004e43c4801
2016-08-25 11:47:17 -07:00
Viktar Donich
78b7cd2191 UI latency reporting
Implements gr-reporting as common reporting interface.
Adds UI latency reporting methods:
 - time and timeEnd to measure individual events.
 - appStarted and pageLoaded to report startup metrics.
 - reporter property at the moment reports metrics via DOM CustomEvents.
 UI Switcher extension listens to those and reports them to Google
 Analytics.

Also, see https://gerrit-review.googlesource.com/83512
Also, go/gerrit-reporting

Change-Id: I36a166f07be281761262fefa50cc539cc6ea2d19
2016-08-22 15:27:51 -07:00
Wyatt Allen
f6d9e66d51 Pre-render the comment thread element
In pages with large diffs, creating the first comment thread can be
slow. With this change, the GR-DIFF-BUILDER makes a hidden thread and
attaches it to the page, then removes it. This causes a much faster
render when the user creates a comment.

Below are some performance numbers based around creating comments in the
large reference diff that is linked in the issue. The measurements are
made on a MacBook Pro with an Intel Core i7, so the difference is all
the more pronounced on slower machines.

                        | Before  | After
    --------------------+---------+-------
          First Comment | ~820 ms | ~95 ms
    --------------------+---------+-------
    Subsequent Comments |  ~50 ms | ~50 ms

Bug: Issue 4335
Change-Id: I649474320afce1b7daa0ad47753bb11223cc305b
2016-08-09 14:15:57 -07:00
Wyatt Allen
5c5f00fb13 Refactors annotation layer
Formerly, the annotation layer interface provided the GrAnnotation
library as a parameter to the `annotate` method. This was so the layer
would not necessarily need to import the library at the module level
and instead could use it as a utility toolbox.

With this change, the library is no-longer part of the interface and the
layers are now expected to import it at the module layer (if they have
a use for it).

Change-Id: I49b96c67ec724708c2861ab6be3ce27a53cc1b05
2016-07-25 22:47:01 -07:00
Wyatt Allen
ea97c5b49a Adds diff traversal helper and make diff object a builder property
Adds a polymorphic method to GrDiffBuilder subclasses named
`_getNextContentOnSide` which gets the a content element by traversing
from its preceding content on the same side. This method is dramatically
faster than the query-based method when diff sections are large.

In preparation for the syntax highlighting change, the gr-diff-builder
is refactored to use a property for the diff object, rather than
accepting it as a parameter to the `render` function.

Tests are included for new functions.

Change-Id: Ifd0edb530a303de2626d55a691c3ba1eaed6167c
2016-07-21 22:26:56 -07:00
Wyatt Allen
d0dd392794 Establish annotation pipeline
Apply diff annotations (intraline differences and comment ranges) by
executing the annotation layers in order to each line. The diff builder
maintains an ordered array of annotation layers which are communicated
to GrDiffBuilder subclass instances. The builder also listens to each
layer for notifications that annotations have changed for some line
range and re-renders (i.e. re-applies the pipeline on DIV.contentText
elements) accordingly.

Change-Id: Iea0599d4869cafaadc0974158153a91d927913e8
2016-07-20 12:25:40 -07:00
Wyatt Allen
117d455ff0 Moved mock-diff-response and fix flaky tests
Moved the mock-diff-response test element to a more-appropriate location
and fixed two tests that were flaky in Safari.

Change-Id: I927973319b200021592b0c9e18c04a902f643894
2016-07-18 10:12:59 -07:00
Wyatt Allen
9317bafd74 Upgrades and tests for GrDiffBuilder utility methods
A new method is added to the GrDiffBuilder class named
`_renderContentByRange`. It will replace DIV.contentText elements with
newly-rendered versions for the given line-range and side.

Two utility methods found on GR-DIFF-BUILDER are pushed down into the
GrDiffBuilder class. In particular, `getContentByLine` is moved as-is
and a wrapper is added to original place. `getContentsByLineRange` is
moved and converted to be more general. A wrapper is put in its original
location which follows the original signature. These moves make the
functions available to other methods of the class.

Tests are added for these new and existing methods.

Change-Id: I77634d05828756c46b802f9ec789ab767faca3cf
2016-07-18 09:30:38 -07:00
Urs Wolfer
33df005810 Fix issues detected by 'JSHint' and 'JSCS'
Change-Id: Ic1437333fcf82473ac57f8bdea25ee8188ddbfee
2016-07-15 20:32:27 +02:00
Wyatt Allen
b7fa5dd929 Applies intraline differences as annotations
The `_addIntralineHighlights` method of GrDiffBuilder is rewritten to
take advantage of GrAnnotate. In particular, formerly, the method would
apply intraline difference highlights by modifying an HTML string.
With this change, the highlight positions are translated as calls to
`GrAnnotate.annotateElement`, which works on the `DIV.contentText`
element.

Change-Id: I2838ef29f057f1108e2ffd196bb48a239366dc87
2016-07-13 21:07:13 -07:00
Wyatt Allen
812f225b79 Dynamic tab width
Previously tabs were configured to all use the same width. However, the
tab width setting is supposed to configure the distance between
tab-stops, not the width of the tabs themselves. Additionally, the width
of a tab element as it's inserted into diff content should be the width
needed to get to the next tab-stop.

Reconfigures the `_textLength` and `_addTabWrappers` methods to respect
this aspect of tab behavior.

Notably, `_addTabWrappers` formerly accepted an HTML string as input,
and was called after intraline differences were applied. With this
change it acts only on raw (non-HTML) strings so that it can directly
determine the position of the tab within the line, and it is called
before intraline differences are applied.

Bug: Issue 4252
Change-Id: I44826d917a505a245fd2b20ccf0ac19378f2806c
2016-07-12 15:25:55 -07:00
Wyatt Allen
1e8216cc35 Upgrade text-length method
The `_textLength` method of gr-diff-builder now correctly handles astral
symbols.

Change-Id: I5e148968cfc3e53733bf6b89703e1cba9117a6d6
2016-07-12 10:32:20 -07:00
Wyatt Allen
7f2bd97901 Separates diff processing from diff building
Moves the diff-processing functionality of the gr-diff-builder component
into a new gr-diff-processor component which exposes a promise-based
interface. This is step one of creating an asynchronous (non-blocking)
diff rendering system.

As much as possible, this change is a transfer of code (with tests) from
one component to another, making it easier to verify that functionality
has not changed. Cleanup of the code, and refactoring it into a
more-testable form will come with later changes.

Feature: Issue 3916
Change-Id: I875b03b20bf953b128cbe3c5001ba1f8eba12c61
2016-06-27 16:44:19 -07:00
Logan Hanks
d2497fbd09 Clean up lint under polygerrit-ui
This change cleans up all lint errors reported by gjslint,
with the exception of third-party code in the gr-linked-text
element and everything under bower_components:

$ gjslint --custom_jsdoc_tags event --check_html \
    -e bower_components,gr-linked-text -r app
Skipping 577 file(s).
181 files checked, no errors found.

Change-Id: I080d58bdd33b2d4b8dd22a717f06eebd7bbfb63d
2016-06-15 14:27:30 -07:00
Viktar Donich
b2198e8233 Ranged comments integration
- gr-file-list recognizes local preferences (for hasRangedComments flag)
- gr-file-list reacts to cursor hotkey only if there is no range
  selected (currently always false).
- Remove dead code from GrDiffBuilderSideBySide, GrDiffBuilder,
  gr-diff-builder.html
- Bugfix: GrDiffBuilder.prototype.getGroupsByLineRange handles one-line
  BOTH code sections correctly. Test updated as well.
- Added utitily methods added to gr-diff-builder.html to reduce
  dependency on DOM structure and reduce amount of code copy-pasting:
  - renderLineRange, getContentByLine, etc
- For gr-diff.js and gr-diff-comment-thread.js addDraft renamed to
  addOrEditDraft because that's what it does.
- For both, addDraft method always creates a draft comment.
- Added support for ranged comments in gr-diff, gr-diff-comment-thread.
- Added mouseenter and mouseout events to gr-comment.js
- Refactored gr-comment.js to reduce code copy-paste, unify event
  payload, and to eliminate need of accessing component instance for
  patchNum. Tests updated as well.
- Refactored gr-diff.js UI data model update using gr-diff-builder.html
  utility methods to make code more readable.
- Added support for creating ranged comments to gr-diff.js.
- gr-selection-action-box now reacts to click and tap to create a 
  comment.

Change-Id: I01480a4c6f460774a8b2826915702800b3f81d25
2016-06-13 22:44:47 +00:00
Viktar Donich
0f02cda57e Make gr-builder a Polymer component
Updated tests, fixed draft comments, context expanding.

Change-Id: Ic4bd9682c63edd8e80fbc2abcb4fa5e406a202ab
2016-06-03 10:22:47 -07:00
Andrew Bonventre
6c9d862e10 Revert "Make gr-builder a Polymer component"
This reverts commit 56689af0f92ce13f90ff9369544c5f9cc0412f09.

Reason for revert: This change broke adding draft comments in the diff
view.

Change-Id: Icfbd3eb4e24cce3e1690e7eaf12e14e5705c7e3e
2016-06-03 13:07:33 +00:00
Viktar Donich
56689af0f9 Make gr-builder a Polymer component
Change-Id: I0d99775332b0ef473459ad481970ad6033427c7d
2016-06-02 13:09:39 -07:00