├── __init__.py ├── VERSION ├── www ├── static │ ├── img │ │ ├── grey.png │ │ └── maps │ │ │ └── README.md │ ├── css │ │ ├── style.css │ │ └── jquery.fancybox.min.css │ └── js │ │ └── jquery.fancybox.min.js └── index.html.orig ├── renovate.json ├── requirements.txt ├── Dockerfile ├── .github └── workflows │ └── main.yml ├── README.md ├── draw.py ├── LICENSE └── pcapplot.py /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.11 2 | -------------------------------------------------------------------------------- /www/static/img/grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IQTLabs/pcapplot/HEAD/www/static/img/grey.png -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | "docker:enableMajor" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cymruwhois==1.6 2 | humanize==3.4.1 3 | Pillow==8.2.0 4 | pika==1.2.0 5 | scapy==2.4.4 6 | -------------------------------------------------------------------------------- /www/static/img/maps/README.md: -------------------------------------------------------------------------------- 1 | Directory where all of the mapped images of the PCAPs get stored as PNGs 2 | -------------------------------------------------------------------------------- /www/static/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, Helvetica, sans-serif; 3 | } 4 | 5 | table { 6 | font-size: 1em; 7 | } 8 | 9 | .ui-draggable, .ui-droppable { 10 | background-position: top; 11 | } 12 | 13 | #wrapper { 14 | width: 100%; 15 | overflow: hidden; 16 | } 17 | #first { 18 | width: 200px; 19 | float:left; 20 | } 21 | #second { 22 | overflow: hidden; 23 | } 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.13 2 | LABEL maintainer="Charlie Lewis " 3 | 4 | RUN apk update --no-cache && apk add --no-cache \ 5 | build-base \ 6 | jpeg-dev \ 7 | python3-dev \ 8 | py3-pip \ 9 | zlib-dev && \ 10 | rm -rf /var/cache/* && \ 11 | rm -rf /root/.cache/* 12 | 13 | COPY VERSION /pcapplot/VERSION 14 | COPY requirements.txt /pcapplot/requirements.txt 15 | RUN pip3 install -r /pcapplot/requirements.txt 16 | COPY . /pcapplot 17 | WORKDIR /pcapplot 18 | 19 | 20 | ENV PYTHONUNBUFFERED 0 21 | ENTRYPOINT ["python3", "pcapplot.py"] 22 | CMD ["/pcaps"] 23 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: buildx 2 | 3 | on: 4 | push: 5 | branches: main 6 | tags: 'v*' 7 | 8 | jobs: 9 | buildx: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Get the version 16 | id: get_version 17 | run: echo ::set-output name=VERSION::$(echo $GITHUB_REF | cut -d / -f 3) 18 | - name: Change for main 19 | id: change_version 20 | run: if [ "${{ steps.get_version.outputs.VERSION }}" == "main" ]; then echo ::set-output name=VERSION::latest; else echo ::set-output name=VERSION::${{ steps.get_version.outputs.VERSION }}; fi 21 | - name: Set up qemu 22 | uses: docker/setup-qemu-action@v1 23 | with: 24 | platforms: all 25 | - name: Set up Docker Buildx 26 | id: buildx 27 | uses: docker/setup-buildx-action@v1 28 | with: 29 | version: latest 30 | - name: Docker Login 31 | env: 32 | DOCKER_PASSWORD: ${{ secrets.DOCKER_TOKEN }} 33 | run: | 34 | echo "${DOCKER_PASSWORD}" | docker login --username "${{ secrets.DOCKER_USERNAME }}" --password-stdin 35 | if: github.repository == 'iqtlabs/pcapplot' && github.event_name == 'push' 36 | - name: Build and push platforms 37 | env: 38 | DOCKER_CLI_EXPERIMENTAL: enabled 39 | run: | 40 | docker buildx build \ 41 | --platform linux/amd64,linux/arm/v7,linux/arm64 \ 42 | --push \ 43 | -t iqtlabs/pcapplot:${{ steps.change_version.outputs.VERSION }} . 44 | if: github.repository == 'iqtlabs/pcapplot' && github.event_name == 'push' 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pcapplot 2 | 3 | This is a tool to visualize PCAPs. It makes a couple assumptions about your 4 | PCAP files. First they should have a naming scheme as follows: 5 | 6 | ``` 7 | ---
.pcap 8 | ``` 9 | 10 | Secondly it assumes your PCAPs have been carved such that all packets in the 11 | capture revolve a particular IP address, naming the one the PCAP has as the 12 | device label in the filename. 13 | 14 | To get started, first ensure you have Docker and Git installed, then build the 15 | image for this tool as follows: 16 | 17 | ``` 18 | git clone https://github.com/IQTLabs/pcapplot 19 | cd pcapplot 20 | docker build -t pcapplot . 21 | ``` 22 | 23 | Once the image is built, you can run the tool in a container and map in your 24 | PCAPs via volumes as follows: 25 | 26 | ``` 27 | docker run -it \ 28 | -v ~/my_local_pcaps:/pcaps \ 29 | -v $(pwd)/www:/pcapplot/www \ 30 | pcapplot \ 31 | /pcaps 32 | ``` 33 | 34 | This tool can take either a directory of PCAPs and process them all (like the 35 | above), or you can specify a single PCAP file if you like. Here's an example to 36 | do the later: 37 | 38 | ``` 39 | docker run -it \ 40 | -v ~/my_local_pcaps:/pcaps \ 41 | -v $(pwd)/www:/pcapplot/www \ 42 | pcapplot \ 43 | iphone-16-09-23.pcap 44 | ``` 45 | 46 | Once the tool is finished processing, it will point you to the location of the 47 | images it has created (note the volume for `www`, which lets you retain your 48 | images after the container exits). If you want to add more data to your 49 | visualization just run the container again with new PCAPs and it will add it to 50 | the visualization (note the same filename will overwrite any pre-existing 51 | results with that filename). 52 | -------------------------------------------------------------------------------- /www/index.html.orig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PCAP Plot 7 | 8 | 9 | 10 | 20 | 21 | 22 | 23 | 31 | 32 | 33 |

Home

34 |

Showing maps for the latest capture of each device type, click on a device name to see all maps for that device type. Click and drag rows to reorder them.

35 |

Maps are laid out left to right, top to bottom. ASN is 1-65536, RFC 1918 is 10.0.0.0/8 followed by 172.16.0.0/12 followed by 192.168.0.0/16 (each square is a /24), Source and Destination Ports are 1-65536.

36 |

Blue is inbound traffic, Red is outbound traffic, Green is mostly bidirectional traffic.

37 |
    38 | 39 |
40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /draw.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from PIL import ImageDraw 3 | 4 | import copy 5 | 6 | 7 | def interpolate_tuple(startcolor, goalcolor, steps): 8 | """ 9 | Take two RGB color sets and mix them over a specified number of steps. 10 | Return the list 11 | """ 12 | R = startcolor[0] 13 | G = startcolor[1] 14 | B = startcolor[2] 15 | 16 | targetR = goalcolor[0] 17 | targetG = goalcolor[1] 18 | targetB = goalcolor[2] 19 | 20 | DiffR = targetR - R 21 | DiffG = targetG - G 22 | DiffB = targetB - B 23 | 24 | gradient = [] 25 | 26 | for i in range(0, steps + 1): 27 | iR = R + (DiffR * int(i / steps)) 28 | iG = G + (DiffG * int(i / steps)) 29 | iB = B + (DiffB * int(i / steps)) 30 | 31 | color = (iR,iG,iB) 32 | gradient.append(color) 33 | 34 | return gradient 35 | 36 | def draw(grid, grid_type, ROWS=256, COLUMNS=256, GRID_LINE=16): 37 | BLACK = (0, 0, 0) 38 | WHITE = (255, 255, 255) 39 | GREEN = (0, 255, 0) 40 | RED = (255, 0, 0) 41 | BLUE = (0, 0, 255) 42 | 43 | WIDTH = 9 44 | HEIGHT = 9 45 | MARGIN = 1 46 | W_HEIGHT = (ROWS*10)+1 47 | W_WIDTH = (COLUMNS*10)+1 48 | WINDOW_SIZE = (W_HEIGHT, W_WIDTH) 49 | 50 | im = Image.new('RGB', WINDOW_SIZE) 51 | screen = ImageDraw.Draw(im) 52 | screen.rectangle([0, 0, W_WIDTH, W_HEIGHT], fill=WHITE) 53 | 54 | subgrid = [] 55 | for row in range(int(ROWS/GRID_LINE)): 56 | subgrid.append([]) 57 | for column in range(int(COLUMNS/GRID_LINE)): 58 | subgrid[row].append(0) 59 | 60 | # check which grids should be drawn 61 | new_grid = copy.deepcopy(grid) 62 | if grid_type.startswith('ASN-') or grid_type.startswith('Private_RFC_1918-'): 63 | for r in range(int(ROWS/GRID_LINE)): 64 | for c in range(int(COLUMNS/GRID_LINE)): 65 | box_in = 0 66 | box_out = 0 67 | for row in range(int(ROWS/GRID_LINE)): 68 | for column in range(int(COLUMNS/GRID_LINE)): 69 | y = (r*(int(ROWS/GRID_LINE)))+row 70 | x = (c*(int(COLUMNS/GRID_LINE)))+column 71 | box_in += new_grid[y][x][0] 72 | box_out += new_grid[y][x][1] 73 | if new_grid[y][x][0]+new_grid[y][x][1] == 0: 74 | grid[y][x] = 0 75 | else: 76 | in_percent = new_grid[y][x][0]/(float(new_grid[y][x][0]+new_grid[y][x][1])) 77 | if in_percent < 0.45: 78 | grid[y][x] = 2 79 | elif in_percent > 0.55: 80 | grid[y][x] = 1 81 | else: 82 | grid[y][x] = 3 83 | if box_in+box_out > 0: 84 | in_percent = box_in/(float(box_in+box_out)) 85 | if in_percent < 0.45: 86 | subgrid[r][c] = 2 87 | elif in_percent > 0.55: 88 | subgrid[r][c] = 1 89 | else: 90 | subgrid[r][c] = 3 91 | else: 92 | for row in range(ROWS): 93 | for column in range(COLUMNS): 94 | if new_grid[row][column] != 0: 95 | if grid_type.startswith('Source_Ports-'): 96 | subgrid[int(row / GRID_LINE)][int(column / GRID_LINE)] = 2 97 | elif grid_type.startswith('Destination_Ports-'): 98 | subgrid[int(row / GRID_LINE)][int(column / GRID_LINE)] = 1 99 | 100 | # draw grid 101 | for row in range(int(ROWS/GRID_LINE)): 102 | for column in range(int(COLUMNS/GRID_LINE)): 103 | if subgrid[row][column] > 0: 104 | # outbound is red 105 | if subgrid[row][column] == 1: 106 | COLOR = RED 107 | # inbound is blue 108 | elif subgrid[row][column] == 2: 109 | COLOR = BLUE 110 | # between 45-55% equal is green 111 | elif subgrid[row][column] == 3: 112 | COLOR = GREEN 113 | # this case should never happen 114 | else: 115 | COLOR = BLACK 116 | if int(ROWS/GRID_LINE) == 17: 117 | for i in range(16): 118 | for j in range(16): 119 | dims = [((MARGIN + WIDTH) * column * GRID_LINE + MARGIN-1) + (j * 10), 120 | ((MARGIN + HEIGHT) * row * GRID_LINE + MARGIN-1) + (i * 10), 121 | ((MARGIN + WIDTH) * column * GRID_LINE + MARGIN-1) + ((j+1) * 10), 122 | ((MARGIN + HEIGHT) * row * GRID_LINE + MARGIN-1) + ((i+1) * 10)] 123 | screen.rectangle(dims, fill=WHITE, outline=COLOR, width=MARGIN) 124 | else: 125 | for i in range(16): 126 | for j in range(16): 127 | dims = [((MARGIN + WIDTH) * column * GRID_LINE + MARGIN-1) + (j * 10), 128 | ((MARGIN + HEIGHT) * row * GRID_LINE + MARGIN-1) + (i * 10), 129 | ((MARGIN + WIDTH) * column * GRID_LINE + MARGIN-1) + ((j+1) * 10), 130 | ((MARGIN + HEIGHT) * row * GRID_LINE + MARGIN-1) + ((i+1) * 10)] 131 | screen.rectangle(dims, fill=WHITE, outline=COLOR, width=MARGIN) 132 | 133 | # draw cells 134 | for row in range(ROWS): 135 | for column in range(COLUMNS): 136 | color = WHITE 137 | if grid[row][column] == 1: 138 | color = RED 139 | elif grid[row][column] == 2: 140 | color = BLUE 141 | elif grid[row][column] == 3: 142 | color = GREEN 143 | dims = [(MARGIN + WIDTH) * column + MARGIN, 144 | (MARGIN + HEIGHT) * row + MARGIN, 145 | ((MARGIN + WIDTH) * column + MARGIN) + WIDTH, 146 | ((MARGIN + HEIGHT) * row + MARGIN) + HEIGHT] 147 | if color != WHITE: 148 | screen.rectangle(dims, fill=color) 149 | 150 | im.save("www/static/img/maps/map_" + "_".join(grid_type.split()) + ".png") 151 | return "www/static/img/maps/map_" + "_".join(grid_type.split()) + ".png" 152 | 153 | if __name__ == "__main__": 154 | print(interpolate_tuple((0,255,255), (255,0,0), 100)) 155 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright (c) 2018 IQT Labs LLC, All Rights Reserved. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /www/static/css/jquery.fancybox.min.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";body.fancybox-active{overflow:hidden}body.fancybox-iosfix{position:fixed;left:0;right:0}.fancybox-is-hidden{position:absolute;top:-9999px;left:-9999px;visibility:hidden}.fancybox-container{position:fixed;top:0;left:0;width:100%;height:100%;z-index:99992;-webkit-tap-highlight-color:transparent;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateZ(0);transform:translateZ(0);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-stage{position:absolute;top:0;right:0;bottom:0;left:0}.fancybox-outer{overflow-y:auto;-webkit-overflow-scrolling:touch}.fancybox-bg{background:#1e1e1e;opacity:0;transition-duration:inherit;transition-property:opacity;transition-timing-function:cubic-bezier(.47,0,.74,.71)}.fancybox-is-open .fancybox-bg{opacity:.87;transition-timing-function:cubic-bezier(.22,.61,.36,1)}.fancybox-caption-wrap,.fancybox-infobar,.fancybox-toolbar{position:absolute;direction:ltr;z-index:99997;opacity:0;visibility:hidden;transition:opacity .25s,visibility 0s linear .25s;box-sizing:border-box}.fancybox-show-caption .fancybox-caption-wrap,.fancybox-show-infobar .fancybox-infobar,.fancybox-show-toolbar .fancybox-toolbar{opacity:1;visibility:visible;transition:opacity .25s,visibility 0s}.fancybox-infobar{top:0;left:0;font-size:13px;padding:0 10px;height:44px;min-width:44px;line-height:44px;color:#ccc;text-align:center;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent;-webkit-font-smoothing:subpixel-antialiased;mix-blend-mode:exclusion}.fancybox-toolbar{top:0;right:0;margin:0;padding:0}.fancybox-stage{overflow:hidden;direction:ltr;z-index:99994;-webkit-transform:translateZ(0)}.fancybox-is-closing .fancybox-stage{overflow:visible}.fancybox-slide{position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;overflow:auto;outline:none;white-space:normal;box-sizing:border-box;text-align:center;z-index:99994;-webkit-overflow-scrolling:touch;display:none;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition-property:opacity,-webkit-transform;transition-property:transform,opacity;transition-property:transform,opacity,-webkit-transform}.fancybox-slide:before{content:"";display:inline-block;vertical-align:middle;height:100%;width:0}.fancybox-is-sliding .fancybox-slide,.fancybox-slide--current,.fancybox-slide--next,.fancybox-slide--previous{display:block}.fancybox-slide--image{overflow:visible}.fancybox-slide--image:before{display:none}.fancybox-slide--video .fancybox-content,.fancybox-slide--video iframe{background:#000}.fancybox-slide--map .fancybox-content,.fancybox-slide--map iframe{background:#e5e3df}.fancybox-slide--next{z-index:99995}.fancybox-slide>*{display:inline-block;position:relative;padding:24px;margin:44px 0;border-width:0;vertical-align:middle;text-align:left;background-color:#fff;overflow:auto;box-sizing:border-box}.fancybox-slide>base,.fancybox-slide>link,.fancybox-slide>meta,.fancybox-slide>script,.fancybox-slide>style,.fancybox-slide>title{display:none}.fancybox-slide .fancybox-image-wrap{position:absolute;top:0;left:0;margin:0;padding:0;border:0;z-index:99995;background:transparent;cursor:default;overflow:visible;-webkit-transform-origin:top left;transform-origin:top left;background-size:100% 100%;background-repeat:no-repeat;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;transition-property:opacity,-webkit-transform;transition-property:transform,opacity;transition-property:transform,opacity,-webkit-transform}.fancybox-can-zoomOut .fancybox-image-wrap{cursor:zoom-out}.fancybox-can-zoomIn .fancybox-image-wrap{cursor:zoom-in}.fancybox-can-drag .fancybox-image-wrap{cursor:-webkit-grab;cursor:grab}.fancybox-is-dragging .fancybox-image-wrap{cursor:-webkit-grabbing;cursor:grabbing}.fancybox-image,.fancybox-spaceball{position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;border:0;max-width:none;max-height:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.fancybox-spaceball{z-index:1}.fancybox-slide--iframe .fancybox-content{padding:0;width:80%;height:80%;max-width:calc(100% - 100px);max-height:calc(100% - 88px);overflow:visible;background:#fff}.fancybox-iframe{display:block;padding:0;border:0;height:100%}.fancybox-error,.fancybox-iframe{margin:0;width:100%;background:#fff}.fancybox-error{padding:40px;max-width:380px;cursor:default}.fancybox-error p{margin:0;padding:0;color:#444;font-size:16px;line-height:20px}.fancybox-button{box-sizing:border-box;display:inline-block;vertical-align:top;width:44px;height:44px;margin:0;padding:10px;border:0;border-radius:0;background:rgba(30,30,30,.6);transition:color .3s ease;cursor:pointer;outline:none}.fancybox-button,.fancybox-button:link,.fancybox-button:visited{color:#ccc}.fancybox-button:focus,.fancybox-button:hover{color:#fff}.fancybox-button[disabled]{color:#ccc;cursor:default;opacity:.6}.fancybox-button svg{display:block;position:relative;overflow:visible;shape-rendering:geometricPrecision}.fancybox-button svg path{fill:currentColor;stroke:currentColor;stroke-linejoin:round;stroke-width:3}.fancybox-button--share svg path{stroke-width:1}.fancybox-button--pause svg path:nth-child(1),.fancybox-button--play svg path:nth-child(2){display:none}.fancybox-button--zoom svg path{fill:transparent}.fancybox-navigation{display:none}.fancybox-show-nav .fancybox-navigation{display:block}.fancybox-navigation button{position:absolute;top:50%;margin:-50px 0 0;z-index:99997;background:transparent;width:60px;height:100px;padding:17px}.fancybox-navigation button:before{content:"";position:absolute;top:30px;right:10px;width:40px;height:40px;background:rgba(30,30,30,.6)}.fancybox-navigation .fancybox-button--arrow_left{left:0}.fancybox-navigation .fancybox-button--arrow_right{right:0}.fancybox-close-small{position:absolute;top:0;right:0;width:40px;height:40px;padding:0;margin:0;border:0;border-radius:0;background:transparent;z-index:10;cursor:pointer}.fancybox-close-small:after{content:"×";position:absolute;top:5px;right:5px;width:30px;height:30px;font:22px/30px Arial,Helvetica Neue,Helvetica,sans-serif;color:#888;font-weight:300;text-align:center;border-radius:50%;border-width:0;background-color:transparent;transition:background-color .25s;box-sizing:border-box;z-index:2}.fancybox-close-small:focus{outline:none}.fancybox-close-small:focus:after{outline:1px dotted #888}.fancybox-close-small:hover:after{color:#555;background:#eee}.fancybox-slide--iframe .fancybox-close-small,.fancybox-slide--image .fancybox-close-small{top:0;right:-40px}.fancybox-slide--iframe .fancybox-close-small:after,.fancybox-slide--image .fancybox-close-small:after{font-size:35px;color:#aaa}.fancybox-slide--iframe .fancybox-close-small:hover:after,.fancybox-slide--image .fancybox-close-small:hover:after{color:#fff;background:transparent}.fancybox-is-scaling .fancybox-close-small,.fancybox-is-zoomable.fancybox-can-drag .fancybox-close-small{display:none}.fancybox-caption-wrap{bottom:0;left:0;right:0;padding:60px 2vw 0;background:linear-gradient(180deg,transparent 0,rgba(0,0,0,.1) 20%,rgba(0,0,0,.2) 40%,rgba(0,0,0,.6) 80%,rgba(0,0,0,.8));pointer-events:none}.fancybox-caption{padding:30px 0;border-top:1px solid hsla(0,0%,100%,.4);font-size:14px;color:#fff;line-height:20px;-webkit-text-size-adjust:none}.fancybox-caption a,.fancybox-caption button,.fancybox-caption select{pointer-events:all;position:relative}.fancybox-caption a{color:#fff;text-decoration:underline}.fancybox-slide>.fancybox-loading{border:6px solid hsla(0,0%,39%,.4);border-top:6px solid hsla(0,0%,100%,.6);border-radius:100%;height:50px;width:50px;-webkit-animation:a .8s infinite linear;animation:a .8s infinite linear;background:transparent;position:absolute;top:50%;left:50%;margin-top:-30px;margin-left:-30px;z-index:99999}@-webkit-keyframes a{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes a{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fancybox-animated{transition-timing-function:cubic-bezier(0,0,.25,1)}.fancybox-fx-slide.fancybox-slide--previous{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);opacity:0}.fancybox-fx-slide.fancybox-slide--next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);opacity:0}.fancybox-fx-slide.fancybox-slide--current{-webkit-transform:translateZ(0);transform:translateZ(0);opacity:1}.fancybox-fx-fade.fancybox-slide--next,.fancybox-fx-fade.fancybox-slide--previous{opacity:0;transition-timing-function:cubic-bezier(.19,1,.22,1)}.fancybox-fx-fade.fancybox-slide--current{opacity:1}.fancybox-fx-zoom-in-out.fancybox-slide--previous{-webkit-transform:scale3d(1.5,1.5,1.5);transform:scale3d(1.5,1.5,1.5);opacity:0}.fancybox-fx-zoom-in-out.fancybox-slide--next{-webkit-transform:scale3d(.5,.5,.5);transform:scale3d(.5,.5,.5);opacity:0}.fancybox-fx-zoom-in-out.fancybox-slide--current{-webkit-transform:scaleX(1);transform:scaleX(1);opacity:1}.fancybox-fx-rotate.fancybox-slide--previous{-webkit-transform:rotate(-1turn);transform:rotate(-1turn);opacity:0}.fancybox-fx-rotate.fancybox-slide--next{-webkit-transform:rotate(1turn);transform:rotate(1turn);opacity:0}.fancybox-fx-rotate.fancybox-slide--current{-webkit-transform:rotate(0deg);transform:rotate(0deg);opacity:1}.fancybox-fx-circular.fancybox-slide--previous{-webkit-transform:scale3d(0,0,0) translate3d(-100%,0,0);transform:scale3d(0,0,0) translate3d(-100%,0,0);opacity:0}.fancybox-fx-circular.fancybox-slide--next{-webkit-transform:scale3d(0,0,0) translate3d(100%,0,0);transform:scale3d(0,0,0) translate3d(100%,0,0);opacity:0}.fancybox-fx-circular.fancybox-slide--current{-webkit-transform:scaleX(1) translateZ(0);transform:scaleX(1) translateZ(0);opacity:1}.fancybox-fx-tube.fancybox-slide--previous{-webkit-transform:translate3d(-100%,0,0) scale(.1) skew(-10deg);transform:translate3d(-100%,0,0) scale(.1) skew(-10deg)}.fancybox-fx-tube.fancybox-slide--next{-webkit-transform:translate3d(100%,0,0) scale(.1) skew(10deg);transform:translate3d(100%,0,0) scale(.1) skew(10deg)}.fancybox-fx-tube.fancybox-slide--current{-webkit-transform:translateZ(0) scale(1);transform:translateZ(0) scale(1)}.fancybox-share{padding:30px;border-radius:3px;background:#f4f4f4;max-width:90%;text-align:center}.fancybox-share h1{color:#222;margin:0 0 20px;font-size:35px;font-weight:700}.fancybox-share p{margin:0;padding:0}p.fancybox-share__links{margin-right:-10px}.fancybox-share__button{display:inline-block;text-decoration:none;margin:0 10px 10px 0;padding:0 15px;min-width:130px;border:0;border-radius:3px;background:#fff;white-space:nowrap;font-size:14px;font-weight:700;line-height:40px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;color:#fff;transition:all .2s}.fancybox-share__button:hover{text-decoration:none}.fancybox-share__button--fb{background:#3b5998}.fancybox-share__button--fb:hover{background:#344e86}.fancybox-share__button--pt{background:#bd081d}.fancybox-share__button--pt:hover{background:#aa0719}.fancybox-share__button--tw{background:#1da1f2}.fancybox-share__button--tw:hover{background:#0d95e8}.fancybox-share__button svg{position:relative;top:-1px;width:25px;height:25px;margin-right:7px;vertical-align:middle}.fancybox-share__button svg path{fill:#fff}.fancybox-share__input{box-sizing:border-box;width:100%;margin:10px 0 0;padding:10px 15px;background:transparent;color:#5d5b5b;font-size:14px;outline:none;border:0;border-bottom:2px solid #d7d7d7}.fancybox-thumbs{display:none;position:absolute;top:0;bottom:0;right:0;width:212px;margin:0;padding:2px 2px 4px;background:#fff;-webkit-tap-highlight-color:transparent;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar;box-sizing:border-box;z-index:99995}.fancybox-thumbs-x{overflow-y:hidden;overflow-x:auto}.fancybox-show-thumbs .fancybox-thumbs{display:block}.fancybox-show-thumbs .fancybox-inner{right:212px}.fancybox-thumbs>ul{list-style:none;position:absolute;position:relative;width:100%;height:100%;margin:0;padding:0;overflow-x:hidden;overflow-y:auto;font-size:0;white-space:nowrap}.fancybox-thumbs-x>ul{overflow:hidden}.fancybox-thumbs-y>ul::-webkit-scrollbar{width:7px}.fancybox-thumbs-y>ul::-webkit-scrollbar-track{background:#fff;border-radius:10px;box-shadow:inset 0 0 6px rgba(0,0,0,.3)}.fancybox-thumbs-y>ul::-webkit-scrollbar-thumb{background:#2a2a2a;border-radius:10px}.fancybox-thumbs>ul>li{float:left;overflow:hidden;padding:0;margin:2px;width:100px;height:75px;max-width:calc(50% - 4px);max-height:calc(100% - 8px);position:relative;cursor:pointer;outline:none;-webkit-tap-highlight-color:transparent;-webkit-backface-visibility:hidden;backface-visibility:hidden;box-sizing:border-box}li.fancybox-thumbs-loading{background:rgba(0,0,0,.1)}.fancybox-thumbs>ul>li>img{position:absolute;top:0;left:0;max-width:none;max-height:none;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.fancybox-thumbs>ul>li:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;border:4px solid #4ea7f9;z-index:99991;opacity:0;transition:all .2s cubic-bezier(.25,.46,.45,.94)}.fancybox-thumbs>ul>li.fancybox-thumbs-active:before{opacity:1}@media (max-width:800px){.fancybox-thumbs{width:110px}.fancybox-show-thumbs .fancybox-inner{right:110px}.fancybox-thumbs>ul>li{max-width:calc(100% - 10px)}} -------------------------------------------------------------------------------- /pcapplot.py: -------------------------------------------------------------------------------- 1 | import http.server 2 | 3 | from cymruwhois import Client 4 | from datetime import datetime 5 | from scapy.utils import PcapReader 6 | from scapy.layers.inet import IP 7 | from subprocess import call 8 | 9 | import ast 10 | import base64 11 | import copy 12 | import humanize 13 | import json 14 | import os 15 | import pika 16 | import shutil 17 | import signal 18 | import sys 19 | 20 | from draw import draw 21 | 22 | def signal_handler(signal, frame): 23 | os._exit(0) 24 | 25 | def populate_1918_space(): 26 | internal_map = {} 27 | inner = 0 28 | outer = 0 29 | 30 | # 10.x.x.x 31 | for oct1 in range(10,11): 32 | for oct2 in range(0,256): 33 | for oct3 in range(0,256): 34 | internal_map[str(oct1)+'.'+str(oct2)+'.'+str(oct3)] = [outer, inner] 35 | if inner == 289: 36 | outer += 1 37 | inner = 0 38 | else: 39 | inner += 1 40 | # 172.16-31.x.x 41 | for oct1 in range(172,173): 42 | for oct2 in range(16,32): 43 | for oct3 in range(0,256): 44 | internal_map[str(oct1)+'.'+str(oct2)+'.'+str(oct3)] = [outer, inner] 45 | if inner == 289: 46 | outer += 1 47 | inner = 0 48 | else: 49 | inner += 1 50 | 51 | # 192.168.x.x 52 | for oct1 in range(192,193): 53 | for oct2 in range(168,169): 54 | for oct3 in range(0,256): 55 | internal_map[str(oct1)+'.'+str(oct2)+'.'+str(oct3)] = [outer, inner] 56 | if inner == 289: 57 | outer += 1 58 | inner = 0 59 | else: 60 | inner += 1 61 | 62 | return internal_map 63 | 64 | def ip_class(ip): 65 | classes = ['public', # 0 66 | '0.0.0.0/8', # 1 67 | '10.0.0.0/8', # 2 68 | '127.0.0.0/8', # 3 69 | '169.254.0.0/16', # 4 70 | '172.16.0.0/12', # 5 71 | '192.0.0.0/24', # 6 72 | '192.0.2.0/24', # 7 73 | '192.18.0.0/15', # 8 74 | '192.88.99.0/24', # 9 75 | '192.168.0.0/16', # 10 76 | '198.51.100.0/24', # 11 77 | '203.0.113.0/24', # 12 78 | '224.0.0.0/4', # 13 79 | '240.0.0.0/4' # 14 80 | ] 81 | result = '' 82 | stripped_ip = ip.split(".") 83 | oct1 = stripped_ip[0] 84 | oct2 = stripped_ip[1] 85 | oct3 = stripped_ip[2] 86 | oct4 = stripped_ip[3] 87 | 88 | if int(oct1) == 0: 89 | result = classes[1] 90 | elif int(oct1) in range(1,10): 91 | result = classes[0] 92 | elif int(oct1) == 10: 93 | result = classes[2] 94 | elif int(oct1) in range(11,127): 95 | result = classes[0] 96 | elif int(oct1) == 127: 97 | result = classes[3] 98 | elif int(oct1) == 169 and int(oct2) == 254: 99 | result = classes[4] 100 | elif int(oct1) in range(128,172): 101 | result = classes[0] 102 | elif int(oct1) == 172 and int(oct2) in range(0,16): 103 | result = classes[0] 104 | elif int(oct1) == 172 and int(oct2) in range(16,32): 105 | result = classes[5] 106 | elif int(oct1) == 172 and int(oct2) in range(32,256): 107 | result = classes[0] 108 | elif int(oct1) in range(173,192): 109 | result = classes[0] 110 | elif int(oct1) == 192 and int(oct2) == 0 and int(oct3) == 0: 111 | result = classes[6] 112 | elif int(oct1) == 192 and int(oct2) == 0 and int(oct3) == 1: 113 | result = classes[0] 114 | elif int(oct1) == 192 and int(oct2) == 0 and int(oct3) == 2: 115 | result = classes[7] 116 | elif int(oct1) == 192 and int(oct2) == 0 and int(oct3) in range(3,256): 117 | result = classes[0] 118 | elif int(oct1) == 192 and int(oct2) in range(1,18): 119 | result = classes[0] 120 | elif int(oct1) == 192 and int(oct2) in range(18,20): 121 | result = classes[8] 122 | elif int(oct1) == 192 and int(oct2) in range(20,88): 123 | result = classes[0] 124 | elif int(oct1) == 192 and int(oct2) == 88 and int(oct3) in range(0,99): 125 | result = classes[0] 126 | elif int(oct1) == 192 and int(oct2) == 88 and int(oct3) == 99: 127 | result = classes[9] 128 | elif int(oct1) == 192 and int(oct2) == 88 and int(oct3) in range(100,256): 129 | result = classes[0] 130 | elif int(oct1) == 192 and int(oct2) in range(89,168): 131 | result = classes[0] 132 | elif int(oct1) == 192 and int(oct2) == 168: 133 | result = classes[10] 134 | elif int(oct1) == 192 and int(oct2) in range(169,256): 135 | result = classes[0] 136 | elif int(oct1) in range(193,198): 137 | result = classes[0] 138 | elif int(oct1) == 198 and int(oct2) in range(0,51): 139 | result = classes[0] 140 | elif int(oct1) == 198 and int(oct2) == 51 and int(oct3) in range(0,100): 141 | result = classes[0] 142 | elif int(oct1) == 198 and int(oct2) == 51 and int(oct3) == 100: 143 | result = classes[11] 144 | elif int(oct1) == 198 and int(oct2) == 51 and int(oct3) in range(101,256): 145 | result = classes[0] 146 | elif int(oct1) == 198 and int(oct2) in range(52,256): 147 | result = classes[0] 148 | elif int(oct1) in range(199,203): 149 | result = classes[0] 150 | elif int(oct1) == 203 and int(oct2) == 0 and int(oct3) in range(0,113): 151 | result = classes[0] 152 | elif int(oct1) == 203 and int(oct2) == 0 and int(oct3) == 113: 153 | result = classes[12] 154 | elif int(oct1) == 203 and int(oct2) == 0 and int(oct3) in range(114,256): 155 | result = classes[0] 156 | elif int(oct1) == 203 and int(oct2) in range(1,256): 157 | result = classes[0] 158 | elif int(oct1) in range(204,224): 159 | result = classes[0] 160 | elif int(oct1) in range(224,240): 161 | result = classes[13] 162 | elif int(oct1) in range(240,256): 163 | result = classes[14] 164 | # anything else is not a valid ipv4 address 165 | return result 166 | 167 | def process_pcaps(pcap_file): 168 | aggr_dict = {} 169 | 170 | ROWS = 256 171 | COLUMNS = 256 172 | 173 | sport_grid = [] 174 | for row in range(ROWS): 175 | sport_grid.append([]) 176 | for column in range(COLUMNS): 177 | sport_grid[row].append(0) 178 | 179 | dport_grid = [] 180 | for row in range(ROWS): 181 | dport_grid.append([]) 182 | for column in range(COLUMNS): 183 | dport_grid[row].append(0) 184 | 185 | print("Reading pcap file " + pcap_file + "...") 186 | sys.stdout.flush() 187 | 188 | proto_dict = {17:'UDP', 6:'TCP'} 189 | ip_dports = {} 190 | packet_count = 0 191 | start_time = None 192 | end_time = None 193 | with PcapReader(pcap_file) as packets: 194 | for packet in packets: 195 | try: 196 | if (IP in packet) and (packet.proto in proto_dict.keys()): 197 | if packet_count == 0: 198 | start_time = packet.time 199 | else: 200 | end_time = packet.time 201 | packet_count += 1 202 | proto_name = proto_dict[packet.proto] 203 | l3 = packet['IP'] 204 | l4 = packet[proto_name] 205 | if (l3.src != '0.0.0.0' and l3.src != '255.255.255.255' and 206 | l3.dst != '0.0.0.0' and l3.dst != '255.255.255.255'): 207 | if l3.src not in aggr_dict: 208 | aggr_dict[l3.src] = {} 209 | if l3.dst not in aggr_dict[l3.src]: 210 | aggr_dict[l3.src][l3.dst] = 0 211 | aggr_dict[l3.src][l3.dst] += len(packet.payload) 212 | 213 | # get ports 214 | if l3.src not in ip_dports: 215 | ip_dports[l3.src] = [] 216 | ip_dports[l3.src].append(l4.dport) 217 | except: 218 | # packet failed to parse, skipping 219 | pass 220 | 221 | print("done") 222 | 223 | ROWS = 289 224 | COLUMNS = 289 225 | 226 | private_grid = [] 227 | for row in range(ROWS): 228 | private_grid.append([]) 229 | for column in range(COLUMNS): 230 | private_grid[row].append([0, 0]) 231 | 232 | private_map = populate_1918_space() 233 | 234 | ROWS = 256 235 | COLUMNS = 256 236 | asn_dict = {} 237 | c = Client() 238 | for host in aggr_dict: 239 | if len(aggr_dict[host]) > 1: 240 | # get sent bytes 241 | print("host: {0}".format(host)) 242 | with open('www/static/img/maps/manifest.txt', 'a+') as f: 243 | f.write(pcap_file.split("/")[-1] + ": " + host + "\n") 244 | for port in ip_dports[host]: 245 | dport_grid[int(port/ROWS)][port%ROWS] = 1 246 | for peer in aggr_dict[host]: 247 | try: 248 | r = c.lookup(peer) 249 | if not r.asn: 250 | if not r.cc: 251 | # RFC 1918, etc. 252 | #print "peer:", peer, "bytes out :", aggr_dict[host][peer] 253 | priv_arr = private_map[".".join(peer.split(".")[:-1])] 254 | private_grid[priv_arr[0]][priv_arr[1]][1] += aggr_dict[host][peer] 255 | else: 256 | print("found public IP without an ASN: {0} bytes out: {1}".format(peer, aggr_dict[host][peer])) 257 | else: 258 | # public ip space 259 | if r.asn in asn_dict: 260 | asn_dict[r.asn]['bytes_out'] += aggr_dict[host][peer] 261 | else: 262 | asn_dict[r.asn] = {'owner': r.owner, 'bytes_out': aggr_dict[host][peer], 'bytes_in': 0} 263 | except Exception as e: 264 | print("{0} FAILED TO LOOKUP ASN".format(peer)) 265 | print(str(sys.exc_info()[0]) + str(e)) 266 | else: 267 | if host in ip_dports: 268 | for port in ip_dports[host]: 269 | sport_grid[int(port/ROWS)][port%ROWS] = 2 270 | # get received bytes 271 | dst = None 272 | # there is only one to loop through 273 | for d in aggr_dict[host]: 274 | dst = d 275 | try: 276 | r = c.lookup(host) 277 | if not r.asn: 278 | if not r.cc: 279 | # RFC 1918, etc. 280 | #print "peer:", host, "bytes in:", aggr_dict[host][dst] 281 | priv_arr = private_map[".".join(host.split(".")[:-1])] 282 | private_grid[priv_arr[0]][priv_arr[1]][0] += aggr_dict[host][dst] 283 | else: 284 | print("found public IP without an ASN: {0} bytes out: {1}".format(host, aggr_dict[host][dst])) 285 | else: 286 | # public ip space 287 | if r.asn in asn_dict: 288 | asn_dict[r.asn]['bytes_in'] += aggr_dict[host][dst] 289 | else: 290 | asn_dict[r.asn] = {'owner': r.owner, 'bytes_in': aggr_dict[host][dst], 'bytes_out': 0} 291 | except Exception as e: 292 | print("{0} FAILED TO LOOKUP ASN".format(host)) 293 | print(str(sys.exc_info()[0]) + str(e)) 294 | 295 | asn_grid = [] 296 | for row in range(ROWS): 297 | asn_grid.append([]) 298 | for column in range(COLUMNS): 299 | asn_grid[row].append([0, 0]) 300 | 301 | for asn in asn_dict: 302 | try: 303 | asn_num = int(asn) 304 | if asn_num < 65536: 305 | asn_grid[int(asn_num/ROWS)][asn_num%ROWS] = [asn_dict[asn]['bytes_in'], asn_dict[asn]['bytes_out']] 306 | else: 307 | print("ALERT!!!! high external asn: {0} asn owner: {1} total bytes sent: {2} total bytes received: {3}".format(asn, asn_dict[asn]['owner'], asn_dict[asn]['bytes_out'], asn_dict[asn]['bytes_in'])) 308 | except Exception as e: 309 | print(str(e)) 310 | return asn_grid, private_grid, sport_grid, dport_grid, packet_count, humanize.naturaldelta(datetime.utcfromtimestamp(end_time) - datetime.utcfromtimestamp(start_time)) 311 | 312 | def build_html(pcap_stats): 313 | list_obj = """ 314 |
  • 315 |
    316 |

    %s

    317 |
    318 | 324 | 325 | 331 | 332 | 338 | 339 | 345 | 346 |
    347 |
    348 |
  • 349 | """ 350 | legend = """%s
    Host: %s
    Filename: %s
    Packets: %s
    Time window: %s

    Left to right:

     • Public ASN
     • Private RFC 1918
     • Source Ports
     • Destination Ports""" 351 | image_paths = [] 352 | for root, dirs, files in os.walk('www/static/img/maps'): 353 | for file in files: 354 | if file.endswith(".png"): 355 | image_paths.append(os.path.join(root, file)) 356 | devices = {} 357 | for image in image_paths: 358 | try: 359 | if "-".join(image.split('-')[1:-3]) != '': 360 | if "-".join(image.split('-')[1:-3]) not in devices: 361 | devices["-".join(image.split('-')[1:-3])] = [] 362 | devices["-".join(image.split('-')[1:-3])].append("-".join(image.split('.')[0].split('-')[-3:])) 363 | else: 364 | print("unexpected filename format, ignoring") 365 | except Exception as e: 366 | print(str(e)) 367 | print("unexpected filename format, ignoring") 368 | 369 | # sort images per device 370 | dev_copy = copy.deepcopy(devices) 371 | for device in dev_copy: 372 | devices[device] = sorted(list(set(dev_copy[device]))) 373 | 374 | # create main page 375 | shutil.copy('www/index.html.orig', 'www/index.html') 376 | html_str = "" 377 | 378 | for device in devices: 379 | capture = device+'-'+devices[device][-1]+'.pcap' 380 | host = '' 381 | with open('www/static/img/maps/manifest.txt', 'r') as f: 382 | for line in f: 383 | if line.startswith(capture): 384 | host = line.split(": ")[1].strip() 385 | tmp_legend = legend % (''+device+'', host, capture[:40]+'...', pcap_stats[capture][0], pcap_stats[capture][1]) 386 | prefix = 'static/img/maps/' 387 | asn_path = 'map_ASN-'+device+'-'+devices[device][-1]+'.pcap.png' 388 | private_path = 'map_Private_RFC_1918-'+device+'-'+devices[device][-1]+'.pcap.png' 389 | src_path = 'map_Source_Ports-'+device+'-'+devices[device][-1]+'.pcap.png' 390 | dst_path = 'map_Destination_Ports-'+device+'-'+devices[device][-1]+'.pcap.png' 391 | 392 | html_str += list_obj % (tmp_legend, prefix+asn_path, device, capture, 393 | prefix+asn_path, prefix+asn_path, 394 | prefix+private_path, device, capture, 395 | prefix+private_path, prefix+private_path, 396 | prefix+src_path, device, capture, 397 | prefix+src_path, prefix+src_path, 398 | prefix+dst_path, device, capture, 399 | prefix+dst_path, prefix+dst_path) 400 | with open('www/index.html', 'r') as f: 401 | filedata = f.read() 402 | filedata = filedata.replace("", html_str) 403 | with open('www/index.html', 'w') as f: 404 | f.write(filedata) 405 | 406 | # create device pages 407 | for device in devices: 408 | shutil.copy('www/index.html.orig', 'www/'+device+'.html') 409 | device_html_str = "" 410 | for cap in devices[device]: 411 | capture = device+'-'+cap+'.pcap' 412 | host = '' 413 | with open('www/static/img/maps/manifest.txt', 'r') as f: 414 | for line in f: 415 | if line.startswith(capture): 416 | host = line.split(": ")[1].strip() 417 | tmp_legend = legend % (device, host, capture, pcap_stats[capture][0], pcap_stats[capture][1]) 418 | prefix = 'static/img/maps/' 419 | asn_path = 'map_ASN-'+device+'-'+cap+'.pcap.png' 420 | private_path = 'map_Private_RFC_1918-'+device+'-'+cap+'.pcap.png' 421 | src_path = 'map_Source_Ports-'+device+'-'+cap+'.pcap.png' 422 | dst_path = 'map_Destination_Ports-'+device+'-'+cap+'.pcap.png' 423 | 424 | device_html_str += list_obj % (tmp_legend, prefix+asn_path, device, 425 | capture, prefix+asn_path, 426 | prefix+asn_path, prefix+private_path, 427 | device, capture, prefix+private_path, 428 | prefix+private_path, prefix+src_path, 429 | device, capture, prefix+src_path, 430 | prefix+src_path, prefix+dst_path, 431 | device, capture, prefix+dst_path, 432 | prefix+dst_path) 433 | with open('www/'+device+'.html', 'r') as f: 434 | filedata = f.read() 435 | filedata = filedata.replace("", device_html_str) 436 | with open('www/'+device+'.html', 'w') as f: 437 | f.write(filedata) 438 | return 439 | 440 | def build_images(pcaps, processed_pcaps, pcap_stats, rabbit=False, rabbit_host='messenger'): 441 | if rabbit: 442 | channel = connect_rabbit(host=rabbit_host) 443 | for pcap_file in pcaps: 444 | try: 445 | images = [] 446 | asn_grid, private_grid, sport_grid, dport_grid, packet_count, time_delta = process_pcaps(pcap_file) 447 | images.append(draw(asn_grid, "ASN-"+pcap_file.split("/")[-1])) 448 | images.append(draw(private_grid, "Private_RFC_1918-"+pcap_file.split("/")[-1], ROWS=289, COLUMNS=289, GRID_LINE=17)) 449 | images.append(draw(sport_grid, "Source_Ports-"+pcap_file.split("/")[-1])) 450 | images.append(draw(dport_grid, "Destination_Ports-"+pcap_file.split("/")[-1])) 451 | processed_pcaps.append(pcap_file) 452 | host = '' 453 | with open('www/static/img/maps/manifest.txt', 'r') as f: 454 | for line in f: 455 | if line.startswith(pcap_file.split("/")[-1]): 456 | host = line.split(": ")[1].strip() 457 | pcap_stats[pcap_file.split("/")[-1]] = (packet_count, str(time_delta), host) 458 | if rabbit: 459 | uid = os.getenv('id', 'None') 460 | file_path = os.getenv('file_path', 'None') 461 | for counter, image in enumerate(images): 462 | with open(image, 'rb') as f: 463 | encoded_string = base64.b64encode(f.read()) 464 | body = {'id': uid, 'type': 'data', 'img_path': image, 'data': encoded_string.decode('utf-8'), 'file_path': file_path, 'pcap': os.path.split(pcap_file)[-1], 'results': {'counter': counter+1, 'total': len(images), 'tool': 'pcapplot', 'version': get_version()}} 465 | send_rabbit_msg(body, channel) 466 | data = {} 467 | data['pcap'] = os.path.split(pcap_file)[-1] 468 | data[pcap_file.split("/")[-1]] = (packet_count, str(time_delta), host) 469 | body = {'id': uid, 'type': 'metadata', 'file_path': file_path, 'data': data, 'results': {'tool': 'pcapplot', 'version': get_version()}} 470 | send_rabbit_msg(body, channel) 471 | except Exception as e: 472 | print(str(e)) 473 | exc_type, exc_obj, exc_tb = sys.exc_info() 474 | fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] 475 | print(str(exc_type) + str(fname) + str(exc_tb.tb_lineno)) 476 | if rabbit: 477 | uid = os.getenv('id', 'None') 478 | file_path = os.getenv('file_path', 'None') 479 | body = {'id': uid, 'type': 'metadata', 'file_path': file_path, 'data': '', 'results': {'tool': 'pcapplot', 'version': get_version()}} 480 | send_rabbit_msg(body, channel) 481 | return processed_pcaps, pcap_stats 482 | 483 | def connect_rabbit(host='messenger', port=5672, queue='task_queue'): 484 | params = pika.ConnectionParameters(host=host, port=port) 485 | connection = pika.BlockingConnection(params) 486 | channel = connection.channel() 487 | channel.queue_declare(queue=queue, durable=True) 488 | return channel 489 | 490 | def send_rabbit_msg(msg, channel, exchange='', routing_key='task_queue'): 491 | channel.basic_publish(exchange=exchange, 492 | routing_key=routing_key, 493 | body=json.dumps(msg), 494 | properties=pika.BasicProperties( 495 | delivery_mode=2, 496 | )) 497 | print(" [X] %s UTC %r %r" % (str(datetime.utcnow()), 498 | str(msg['id']), str(msg['file_path']))) 499 | return 500 | 501 | def get_version(): 502 | version = '' 503 | with open('VERSION', 'r') as f: 504 | for line in f: 505 | version = line.strip() 506 | return version 507 | 508 | def main(): 509 | signal.signal(signal.SIGINT, signal_handler) 510 | pcaps = [] 511 | processed_pcaps = [] 512 | pcap_stats = {} 513 | path = sys.argv[1] 514 | if path.endswith('.pcap'): 515 | pcaps.append(path) 516 | else: 517 | print(path) 518 | try: 519 | pcaps = ast.literal_eval(path) 520 | except: 521 | for root, dirs, files in os.walk(path): 522 | for file in files: 523 | if file.endswith(".pcap") and 'miscellaneous' not in file: 524 | pcaps.append(os.path.join(root, file)) 525 | 526 | if pcaps: 527 | print("Found the following PCAP files:") 528 | for pcap_file in pcaps: 529 | print(pcap_file) 530 | 531 | if 'rabbit' in os.environ and os.environ['rabbit'] == 'true': 532 | processed_pcaps, pcap_stats = build_images(pcaps, processed_pcaps, pcap_stats, rabbit=True) 533 | else: 534 | processed_pcaps, pcap_stats = build_images(pcaps, processed_pcaps, pcap_stats) 535 | pcaps = list(set(pcaps)-set(processed_pcaps)) 536 | if pcaps: 537 | print("FAILURE, remaining pcaps: ") 538 | print(pcaps) 539 | print("Try again with the remaining PCAPs with this command:") 540 | print('python pcapplot.py "' + str(pcaps) + '"') 541 | return 542 | 543 | print("Images are located in: 'www/static/img/maps'") 544 | 545 | build_html(pcap_stats) 546 | 547 | return 548 | 549 | if __name__ == "__main__": 550 | main() 551 | -------------------------------------------------------------------------------- /www/static/js/jquery.fancybox.min.js: -------------------------------------------------------------------------------- 1 | // ================================================== 2 | // fancyBox v3.2.10 3 | // 4 | // Licensed GPLv3 for open source use 5 | // or fancyBox Commercial License for commercial use 6 | // 7 | // http://fancyapps.com/fancybox/ 8 | // Copyright 2017 fancyApps 9 | // 10 | // ================================================== 11 | !function(t,e,n,o){"use strict";function i(t){var e=n(t.currentTarget),o=t.data?t.data.options:{},i=e.attr("data-fancybox")||"",a=0,s=[];t.isDefaultPrevented()||(t.preventDefault(),i?(s=o.selector?n(o.selector):t.data?t.data.items:[],s=s.length?s.filter('[data-fancybox="'+i+'"]'):n('[data-fancybox="'+i+'"]'),a=s.index(e),a<0&&(a=0)):s=[e],n.fancybox.open(s,o,a))}if(n){if(n.fn.fancybox)return void("console"in t&&console.log("fancyBox already initialized"));var a={loop:!1,margin:[44,0],gutter:50,keyboard:!0,arrows:!0,infobar:!0,toolbar:!0,buttons:["slideShow","fullScreen","thumbs","share","close"],idleTime:3,smallBtn:"auto",protect:!1,modal:!1,image:{preload:"auto"},ajax:{settings:{data:{fancybox:!0}}},iframe:{tpl:'',preload:!0,css:{},attr:{scrolling:"auto"}},defaultType:"image",animationEffect:"zoom",animationDuration:500,zoomOpacity:"auto",transitionEffect:"fade",transitionDuration:366,slideClass:"",baseClass:"",baseTpl:'',spinnerTpl:'
    ',errorTpl:'

    {{ERROR}}

    ',btnTpl:{download:'',zoom:'',close:'',smallBtn:'',arrowLeft:'',arrowRight:''},parentEl:"body",autoFocus:!1,backFocus:!0,trapFocus:!0,fullScreen:{autoStart:!1},touch:{vertical:!0,momentum:!0},hash:null,media:{},slideShow:{autoStart:!1,speed:4e3},thumbs:{autoStart:!1,hideOnClose:!0,parentEl:".fancybox-container",axis:"y"},wheel:"auto",onInit:n.noop,beforeLoad:n.noop,afterLoad:n.noop,beforeShow:n.noop,afterShow:n.noop,beforeClose:n.noop,afterClose:n.noop,onActivate:n.noop,onDeactivate:n.noop,clickContent:function(t,e){return"image"===t.type&&"zoom"},clickSlide:"close",clickOutside:"close",dblclickContent:!1,dblclickSlide:!1,dblclickOutside:!1,mobile:{idleTime:!1,margin:0,clickContent:function(t,e){return"image"===t.type&&"toggleControls"},clickSlide:function(t,e){return"image"===t.type?"toggleControls":"close"},dblclickContent:function(t,e){return"image"===t.type&&"zoom"},dblclickSlide:function(t,e){return"image"===t.type&&"zoom"}},lang:"en",i18n:{en:{CLOSE:"Close",NEXT:"Next",PREV:"Previous",ERROR:"The requested content cannot be loaded.
    Please try again later.",PLAY_START:"Start slideshow",PLAY_STOP:"Pause slideshow",FULL_SCREEN:"Full screen",THUMBS:"Thumbnails",DOWNLOAD:"Download",SHARE:"Share",ZOOM:"Zoom"},de:{CLOSE:"Schliessen",NEXT:"Weiter",PREV:"Zurück",ERROR:"Die angeforderten Daten konnten nicht geladen werden.
    Bitte versuchen Sie es später nochmal.",PLAY_START:"Diaschau starten",PLAY_STOP:"Diaschau beenden",FULL_SCREEN:"Vollbild",THUMBS:"Vorschaubilder",DOWNLOAD:"Herunterladen",SHARE:"Teilen",ZOOM:"Maßstab"}}},s=n(t),r=n(e),c=0,l=function(t){return t&&t.hasOwnProperty&&t instanceof n},u=function(){return t.requestAnimationFrame||t.webkitRequestAnimationFrame||t.mozRequestAnimationFrame||t.oRequestAnimationFrame||function(e){return t.setTimeout(e,1e3/60)}}(),d=function(){var t,n=e.createElement("fakeelement"),i={transition:"transitionend",OTransition:"oTransitionEnd",MozTransition:"transitionend",WebkitTransition:"webkitTransitionEnd"};for(t in i)if(n.style[t]!==o)return i[t];return"transitionend"}(),f=function(t){return t&&t.length&&t[0].offsetHeight},p=function(t,o,i){var a=this;a.opts=n.extend(!0,{index:i},n.fancybox.defaults,o||{}),n.fancybox.isMobile&&(a.opts=n.extend(!0,{},a.opts,a.opts.mobile)),o&&n.isArray(o.buttons)&&(a.opts.buttons=o.buttons),a.id=a.opts.id||++c,a.group=[],a.currIndex=parseInt(a.opts.index,10)||0,a.prevIndex=null,a.prevPos=null,a.currPos=0,a.firstRun=null,a.createGroup(t),a.group.length&&(a.$lastFocus=n(e.activeElement).blur(),a.slides={},a.init())};n.extend(p.prototype,{init:function(){var i,a,s,c=this,l=c.group[c.currIndex],u=l.opts,d=n.fancybox.scrollbarWidth;c.scrollTop=r.scrollTop(),c.scrollLeft=r.scrollLeft(),n.fancybox.getInstance()||(n("body").addClass("fancybox-active"),/iPad|iPhone|iPod/.test(navigator.userAgent)&&!t.MSStream?"image"!==l.type&&n("body").css("top",n("body").scrollTop()*-1).addClass("fancybox-iosfix"):!n.fancybox.isMobile&&e.body.scrollHeight>t.innerHeight&&(d===o&&(i=n('
    ').appendTo("body"),d=n.fancybox.scrollbarWidth=i[0].offsetWidth-i[0].clientWidth,i.remove()),n("head").append('"),n("body").addClass("compensate-for-scrollbar"))),s="",n.each(u.buttons,function(t,e){s+=u.btnTpl[e]||""}),a=n(c.translate(c,u.baseTpl.replace("{{buttons}}",s).replace("{{arrows}}",u.btnTpl.arrowLeft+u.btnTpl.arrowRight))).attr("id","fancybox-container-"+c.id).addClass("fancybox-is-hidden").addClass(u.baseClass).data("FancyBox",c).appendTo(u.parentEl),c.$refs={container:a},["bg","inner","infobar","toolbar","stage","caption","navigation"].forEach(function(t){c.$refs[t]=a.find(".fancybox-"+t)}),c.trigger("onInit"),c.activate(),c.jumpTo(c.currIndex)},translate:function(t,e){var n=t.opts.i18n[t.opts.lang];return e.replace(/\{\{(\w+)\}\}/g,function(t,e){var i=n[e];return i===o?t:i})},createGroup:function(t){var e=this,i=n.makeArray(t);n.each(i,function(t,i){var a,s,r,c,l,u={},d={};n.isPlainObject(i)?(u=i,d=i.opts||i):"object"===n.type(i)&&n(i).length?(a=n(i),d=a.data(),d=n.extend({},d,d.options||{}),d.$orig=a,u.src=d.src||a.attr("href"),u.type||u.src||(u.type="inline",u.src=i)):u={type:"html",src:i+""},u.opts=n.extend(!0,{},e.opts,d),n.isArray(d.buttons)&&(u.opts.buttons=d.buttons),s=u.type||u.opts.type,c=u.src||"",!s&&c&&(c.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i)?s="image":c.match(/\.(pdf)((\?|#).*)?$/i)?s="pdf":(r=c.match(/\.(mp4|mov|ogv)((\?|#).*)?$/i))?(s="video",u.opts.videoFormat||(u.opts.videoFormat="video/"+("ogv"===r[1]?"ogg":r[1]))):"#"===c.charAt(0)&&(s="inline")),s?u.type=s:e.trigger("objectNeedsType",u),u.index=e.group.length,u.opts.$orig&&!u.opts.$orig.length&&delete u.opts.$orig,!u.opts.$thumb&&u.opts.$orig&&(u.opts.$thumb=u.opts.$orig.find("img:first")),u.opts.$thumb&&!u.opts.$thumb.length&&delete u.opts.$thumb,"function"===n.type(u.opts.caption)&&(u.opts.caption=u.opts.caption.apply(i,[e,u])),"function"===n.type(e.opts.caption)&&(u.opts.caption=e.opts.caption.apply(i,[e,u])),u.opts.caption instanceof n||(u.opts.caption=u.opts.caption===o?"":u.opts.caption+""),"ajax"===s&&(l=c.split(/\s+/,2),l.length>1&&(u.src=l.shift(),u.opts.filter=l.shift())),"auto"==u.opts.smallBtn&&(n.inArray(s,["html","inline","ajax"])>-1?(u.opts.toolbar=!1,u.opts.smallBtn=!0):u.opts.smallBtn=!1),"pdf"===s&&(u.type="iframe",u.opts.iframe.preload=!1),u.opts.modal&&(u.opts=n.extend(!0,u.opts,{infobar:0,toolbar:0,smallBtn:0,keyboard:0,slideShow:0,fullScreen:0,thumbs:0,touch:0,clickContent:!1,clickSlide:!1,clickOutside:!1,dblclickContent:!1,dblclickSlide:!1,dblclickOutside:!1})),e.group.push(u)})},addEvents:function(){var o=this;o.removeEvents(),o.$refs.container.on("click.fb-close","[data-fancybox-close]",function(t){t.stopPropagation(),t.preventDefault(),o.close(t)}).on("click.fb-prev touchend.fb-prev","[data-fancybox-prev]",function(t){t.stopPropagation(),t.preventDefault(),o.previous()}).on("click.fb-next touchend.fb-next","[data-fancybox-next]",function(t){t.stopPropagation(),t.preventDefault(),o.next()}).on("click.fb","[data-fancybox-zoom]",function(t){o[o.isScaledDown()?"scaleToActual":"scaleToFit"]()}),s.on("orientationchange.fb resize.fb",function(t){t&&t.originalEvent&&"resize"===t.originalEvent.type?u(function(){o.update()}):(o.$refs.stage.hide(),setTimeout(function(){o.$refs.stage.show(),o.update()},600))}),r.on("focusin.fb",function(t){var i=n.fancybox?n.fancybox.getInstance():null;i.isClosing||!i.current||!i.current.opts.trapFocus||n(t.target).hasClass("fancybox-container")||n(t.target).is(e)||i&&"fixed"!==n(t.target).css("position")&&!i.$refs.container.has(t.target).length&&(t.stopPropagation(),i.focus(),s.scrollTop(o.scrollTop).scrollLeft(o.scrollLeft))}),r.on("keydown.fb",function(t){var e=o.current,i=t.keyCode||t.which;if(e&&e.opts.keyboard&&!n(t.target).is("input")&&!n(t.target).is("textarea"))return 8===i||27===i?(t.preventDefault(),void o.close(t)):37===i||38===i?(t.preventDefault(),void o.previous()):39===i||40===i?(t.preventDefault(),void o.next()):void o.trigger("afterKeydown",t,i)}),o.group[o.currIndex].opts.idleTime&&(o.idleSecondsCounter=0,r.on("mousemove.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle",function(t){o.idleSecondsCounter=0,o.isIdle&&o.showControls(),o.isIdle=!1}),o.idleInterval=t.setInterval(function(){o.idleSecondsCounter++,o.idleSecondsCounter>=o.group[o.currIndex].opts.idleTime&&!o.isDragging&&(o.isIdle=!0,o.idleSecondsCounter=0,o.hideControls())},1e3))},removeEvents:function(){var e=this;s.off("orientationchange.fb resize.fb"),r.off("focusin.fb keydown.fb .fb-idle"),this.$refs.container.off(".fb-close .fb-prev .fb-next"),e.idleInterval&&(t.clearInterval(e.idleInterval),e.idleInterval=null)},previous:function(t){return this.jumpTo(this.currPos-1,t)},next:function(t){return this.jumpTo(this.currPos+1,t)},jumpTo:function(t,e,i){var a,s,r,c,l,u,d,p=this,h=p.group.length;if(!(p.isDragging||p.isClosing||p.isAnimating&&p.firstRun)){if(t=parseInt(t,10),s=p.current?p.current.opts.loop:p.opts.loop,!s&&(t<0||t>=h))return!1;if(a=p.firstRun=null===p.firstRun,!(h<2&&!a&&p.isDragging)){if(c=p.current,p.prevIndex=p.currIndex,p.prevPos=p.currPos,r=p.createSlide(t),h>1&&((s||r.index>0)&&p.createSlide(t-1),(s||r.indexr.pos?"next":"previous"),c.$slide.removeClass("fancybox-slide--complete fancybox-slide--current fancybox-slide--next fancybox-slide--previous"),c.isComplete=!1,e&&(r.isMoved||r.opts.transitionEffect)&&(r.isMoved?c.$slide.addClass(d):(d="fancybox-animated "+d+" fancybox-fx-"+r.opts.transitionEffect,n.fancybox.animate(c.$slide,d,e,function(){c.$slide.removeClass(d).removeAttr("style")}))))}}},createSlide:function(t){var e,o,i=this;return o=t%i.group.length,o=o<0?i.group.length+o:o,!i.slides[t]&&i.group[o]&&(e=n('
    ').appendTo(i.$refs.stage),i.slides[t]=n.extend(!0,{},i.group[o],{pos:t,$slide:e,isLoaded:!1}),i.updateSlide(i.slides[t])),i.slides[t]},scaleToActual:function(t,e,i){var a,s,r,c,l,u=this,d=u.current,f=d.$content,p=parseInt(d.$slide.width(),10),h=parseInt(d.$slide.height(),10),g=d.width,b=d.height;"image"!=d.type||d.hasError||!f||u.isAnimating||(n.fancybox.stop(f),u.isAnimating=!0,t=t===o?.5*p:t,e=e===o?.5*h:e,a=n.fancybox.getTranslate(f),c=g/a.width,l=b/a.height,s=.5*p-.5*g,r=.5*h-.5*b,g>p&&(s=a.left*c-(t*c-t),s>0&&(s=0),sh&&(r=a.top*l-(e*l-e),r>0&&(r=0),rt.width||o.height>t.height))},isScaledDown:function(){var t=this,e=t.current,o=e.$content,i=!1;return o&&(i=n.fancybox.getTranslate(o),i=i.width1||Math.abs(n.height()-o.height)>1),o},loadSlide:function(t){var e,o,i,a=this;if(!t.isLoading&&!t.isLoaded){switch(t.isLoading=!0,a.trigger("beforeLoad",t),e=t.type,o=t.$slide,o.off("refresh").trigger("onReset").addClass("fancybox-slide--"+(e||"unknown")).addClass(t.opts.slideClass),e){case"image":a.setImage(t);break;case"iframe":a.setIframe(t);break;case"html":a.setContent(t,t.src||t.content);break;case"inline":n(t.src).length?a.setContent(t,n(t.src)):a.setError(t);break;case"ajax":a.showLoading(t),i=n.ajax(n.extend({},t.opts.ajax.settings,{url:t.src,success:function(e,n){"success"===n&&a.setContent(t,e)},error:function(e,n){e&&"abort"!==n&&a.setError(t)}})),o.one("onReset",function(){i.abort()});break;case"video":a.setContent(t,'");break;default:a.setError(t)}return!0}},setImage:function(e){var o,i,a,s,r=this,c=e.opts.srcset||e.opts.image.srcset;if(c){a=t.devicePixelRatio||1,s=t.innerWidth*a,i=c.split(",").map(function(t){var e={};return t.trim().split(/\s+/).forEach(function(t,n){var o=parseInt(t.substring(0,t.length-1),10);return 0===n?e.url=t:void(o&&(e.value=o,e.postfix=t[t.length-1]))}),e}),i.sort(function(t,e){return t.value-e.value});for(var l=0;l=s||"x"===u.postfix&&u.value>=a){o=u;break}}!o&&i.length&&(o=i[i.length-1]),o&&(e.src=o.url,e.width&&e.height&&"w"==o.postfix&&(e.height=e.width/e.height*o.value,e.width=o.value))}e.$content=n('
    ').addClass("fancybox-is-hidden").appendTo(e.$slide),e.opts.preload!==!1&&e.opts.width&&e.opts.height&&(e.opts.thumb||e.opts.$thumb)?(e.width=e.opts.width,e.height=e.opts.height,e.$ghost=n("").one("error",function(){n(this).remove(),e.$ghost=null,r.setBigImage(e)}).one("load",function(){r.afterLoad(e),r.setBigImage(e)}).addClass("fancybox-image").appendTo(e.$content).attr("src",e.opts.thumb||e.opts.$thumb.attr("src"))):r.setBigImage(e)},setBigImage:function(t){var e=this,o=n("");t.$image=o.one("error",function(){e.setError(t)}).one("load",function(){clearTimeout(t.timouts),t.timouts=null,e.isClosing||(t.width=t.opts.width||this.naturalWidth,t.height=t.opts.height||this.naturalHeight,t.opts.image.srcset&&o.attr("sizes","100vw").attr("srcset",t.opts.image.srcset),e.hideLoading(t),t.$ghost?t.timouts=setTimeout(function(){t.timouts=null,t.$ghost.hide()},Math.min(300,Math.max(1e3,t.height/1600))):e.afterLoad(t))}).addClass("fancybox-image").attr("src",t.src).appendTo(t.$content),(o[0].complete||"complete"==o[0].readyState)&&o[0].naturalWidth&&o[0].naturalHeight?o.trigger("load"):o[0].error?o.trigger("error"):t.timouts=setTimeout(function(){o[0].complete||t.hasError||e.showLoading(t)},100)},setIframe:function(t){var e,i=this,a=t.opts.iframe,s=t.$slide;t.$content=n('
    ').css(a.css).appendTo(s),e=n(a.tpl.replace(/\{rnd\}/g,(new Date).getTime())).attr(a.attr).appendTo(t.$content),a.preload?(i.showLoading(t),e.on("load.fb error.fb",function(e){this.isReady=1,t.$slide.trigger("refresh"),i.afterLoad(t)}),s.on("refresh.fb",function(){var n,i,s,r=t.$content,c=a.css.width,l=a.css.height;if(1===e[0].isReady){try{i=e.contents(),s=i.find("body")}catch(t){}s&&s.length&&(c===o&&(n=e[0].contentWindow.document.documentElement.scrollWidth,c=Math.ceil(s.outerWidth(!0)+(r.width()-n)),c+=r.outerWidth()-r.innerWidth()),l===o&&(l=Math.ceil(s.outerHeight(!0)),l+=r.outerHeight()-r.innerHeight()),c&&r.width(c),l&&r.height(l)),r.removeClass("fancybox-is-hidden")}})):this.afterLoad(t),e.attr("src",t.src),t.opts.smallBtn===!0&&t.$content.prepend(i.translate(t,t.opts.btnTpl.smallBtn)),s.one("onReset",function(){try{n(this).find("iframe").hide().attr("src","//about:blank")}catch(t){}n(this).empty(),t.isLoaded=!1})},setContent:function(t,e){var o=this;o.isClosing||(o.hideLoading(t),t.$slide.empty(),l(e)&&e.parent().length?(e.parent(".fancybox-slide--inline").trigger("onReset"),t.$placeholder=n("
    ").hide().insertAfter(e),e.css("display","inline-block")):t.hasError||("string"===n.type(e)&&(e=n("
    ").append(n.trim(e)).contents(),3===e[0].nodeType&&(e=n("
    ").html(e))),t.opts.filter&&(e=n("
    ").html(e).find(t.opts.filter))),t.$slide.one("onReset",function(){n(this).find("video,audio").trigger("pause"),t.$placeholder&&(t.$placeholder.after(e.hide()).remove(),t.$placeholder=null),t.$smallBtn&&(t.$smallBtn.remove(),t.$smallBtn=null),t.hasError||(n(this).empty(),t.isLoaded=!1)}),t.$content=n(e).appendTo(t.$slide),this.afterLoad(t))},setError:function(t){t.hasError=!0,t.$slide.removeClass("fancybox-slide--"+t.type),this.setContent(t,this.translate(t,t.opts.errorTpl))},showLoading:function(t){var e=this;t=t||e.current,t&&!t.$spinner&&(t.$spinner=n(e.opts.spinnerTpl).appendTo(t.$slide))},hideLoading:function(t){var e=this;t=t||e.current,t&&t.$spinner&&(t.$spinner.remove(),delete t.$spinner)},afterLoad:function(t){var e=this;e.isClosing||(t.isLoading=!1,t.isLoaded=!0,e.trigger("afterLoad",t),e.hideLoading(t),t.opts.smallBtn&&!t.$smallBtn&&(t.$smallBtn=n(e.translate(t,t.opts.btnTpl.smallBtn)).appendTo(t.$content.filter("div,form").first())),t.opts.protect&&t.$content&&!t.hasError&&(t.$content.on("contextmenu.fb",function(t){return 2==t.button&&t.preventDefault(),!0}),"image"===t.type&&n('
    ').appendTo(t.$content)),e.revealContent(t))},revealContent:function(t){var e,i,a,s,r,c=this,l=t.$slide,u=!1;return e=t.opts[c.firstRun?"animationEffect":"transitionEffect"],a=t.opts[c.firstRun?"animationDuration":"transitionDuration"],a=parseInt(t.forcedDuration===o?a:t.forcedDuration,10),!t.isMoved&&t.pos===c.currPos&&a||(e=!1),"zoom"!==e||t.pos===c.currPos&&a&&"image"===t.type&&!t.hasError&&(u=c.getThumbPos(t))||(e="fade"),"zoom"===e?(r=c.getFitPos(t),r.scaleX=r.width/u.width,r.scaleY=r.height/u.height,delete r.width,delete r.height,s=t.opts.zoomOpacity,"auto"==s&&(s=Math.abs(t.width/t.height-u.width/u.height)>.1),s&&(u.opacity=.1,r.opacity=1),n.fancybox.setTranslate(t.$content.removeClass("fancybox-is-hidden"),u),f(t.$content),void n.fancybox.animate(t.$content,r,a,function(){c.complete()})):(c.updateSlide(t),e?(n.fancybox.stop(l),i="fancybox-animated fancybox-slide--"+(t.pos>=c.prevPos?"next":"previous")+" fancybox-fx-"+e,l.removeAttr("style").removeClass("fancybox-slide--current fancybox-slide--next fancybox-slide--previous").addClass(i),t.$content.removeClass("fancybox-is-hidden"),f(l),void n.fancybox.animate(l,"fancybox-slide--current",a,function(e){l.removeClass(i).removeAttr("style"),t.pos===c.currPos&&c.complete()},!0)):(f(l),t.$content.removeClass("fancybox-is-hidden"),void(t.pos===c.currPos&&c.complete())))},getThumbPos:function(o){var i,a=this,s=!1,r=function(e){for(var o,i=e[0],a=i.getBoundingClientRect(),s=[];null!==i.parentElement;)"hidden"!==n(i.parentElement).css("overflow")&&"auto"!==n(i.parentElement).css("overflow")||s.push(i.parentElement.getBoundingClientRect()),i=i.parentElement;return o=s.every(function(t){var e=Math.min(a.right,t.right)-Math.max(a.left,t.left),n=Math.min(a.bottom,t.bottom)-Math.max(a.top,t.top);return e>0&&n>0}),o&&a.bottom>0&&a.right>0&&a.left=t.currPos-1&&o.pos<=t.currPos+1?i[o.pos]=o:o&&(n.fancybox.stop(o.$slide),o.$slide.off().remove())}),t.slides=i,t.updateCursor(),t.trigger("afterShow"),o.$slide.find("video,audio").first().trigger("play"),(n(e.activeElement).is("[disabled]")||o.opts.autoFocus&&"image"!=o.type&&"iframe"!==o.type)&&t.focus())},preload:function(t){var e=this,n=e.slides[e.currPos+1],o=e.slides[e.currPos-1];n&&n.type===t&&e.loadSlide(n),o&&o.type===t&&e.loadSlide(o)},focus:function(){var t,e=this.current;this.isClosing||(e&&e.isComplete&&(t=e.$slide.find("input[autofocus]:enabled:visible:first"),t.length||(t=e.$slide.find("button,:input,[tabindex],a").filter(":enabled:visible:first"))),t=t&&t.length?t:this.$refs.container,t.focus())},activate:function(){var t=this;n(".fancybox-container").each(function(){var e=n(this).data("FancyBox");e&&e.id!==t.id&&!e.isClosing&&(e.trigger("onDeactivate"),e.removeEvents(),e.isVisible=!1)}),t.isVisible=!0,(t.current||t.isIdle)&&(t.update(),t.updateControls()),t.trigger("onActivate"),t.addEvents()},close:function(t,e){var o,i,a,s,r,c,l=this,p=l.current,h=function(){l.cleanUp(t)};return!l.isClosing&&(l.isClosing=!0,l.trigger("beforeClose",t)===!1?(l.isClosing=!1,u(function(){l.update()}),!1):(l.removeEvents(),p.timouts&&clearTimeout(p.timouts),a=p.$content,o=p.opts.animationEffect,i=n.isNumeric(e)?e:o?p.opts.animationDuration:0,p.$slide.off(d).removeClass("fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated"),p.$slide.siblings().trigger("onReset").remove(),i&&l.$refs.container.removeClass("fancybox-is-open").addClass("fancybox-is-closing"),l.hideLoading(p),l.hideControls(),l.updateCursor(),"zoom"!==o||t!==!0&&a&&i&&"image"===p.type&&!p.hasError&&(c=l.getThumbPos(p))||(o="fade"),"zoom"===o?(n.fancybox.stop(a),r=n.fancybox.getTranslate(a),r.width=r.width*r.scaleX,r.height=r.height*r.scaleY,s=p.opts.zoomOpacity,"auto"==s&&(s=Math.abs(p.width/p.height-c.width/c.height)>.1),s&&(c.opacity=0),r.scaleX=r.width/c.width,r.scaleY=r.height/c.height,r.width=c.width,r.height=c.height,n.fancybox.setTranslate(p.$content,r),f(p.$content),n.fancybox.animate(p.$content,c,i,h),!0):(o&&i?t===!0?setTimeout(h,i):n.fancybox.animate(p.$slide.removeClass("fancybox-slide--current"),"fancybox-animated fancybox-slide--previous fancybox-fx-"+o,i,h):h(),!0)))},cleanUp:function(t){var o,i,a=this,r=n("body");a.current.$slide.trigger("onReset"),a.$refs.container.empty().remove(),a.trigger("afterClose",t),a.$lastFocus&&a.current.opts.backFocus&&a.$lastFocus.focus(),a.current=null,o=n.fancybox.getInstance(),o?o.activate():(s.scrollTop(a.scrollTop).scrollLeft(a.scrollLeft),r.removeClass("fancybox-active compensate-for-scrollbar"),r.hasClass("fancybox-iosfix")&&(i=parseInt(e.body.style.top,10),r.removeClass("fancybox-iosfix").css("top","").scrollTop(i*-1)),n("#fancybox-style-noscroll").remove())},trigger:function(t,e){var o,i=Array.prototype.slice.call(arguments,1),a=this,s=e&&e.opts?e:a.current;return s?i.unshift(s):s=a,i.unshift(a),n.isFunction(s.opts[t])&&(o=s.opts[t].apply(s,i)),o===!1?o:void("afterClose"!==t&&a.$refs?a.$refs.container.trigger(t+".fb",i):r.trigger(t+".fb",i))},updateControls:function(t){var e=this,n=e.current,o=n.index,i=n.opts.caption,a=e.$refs.container,s=e.$refs.caption;n.$slide.trigger("refresh"),e.$caption=i&&i.length?s.html(i):null,e.isHiddenControls||e.isIdle||e.showControls(),a.find("[data-fancybox-count]").html(e.group.length),a.find("[data-fancybox-index]").html(o+1),a.find("[data-fancybox-prev]").prop("disabled",!n.opts.loop&&o<=0),a.find("[data-fancybox-next]").prop("disabled",!n.opts.loop&&o>=e.group.length-1),"image"===n.type?a.find("[data-fancybox-download]").attr("href",n.opts.image.src||n.src).show():a.find("[data-fancybox-download],[data-fancybox-zoom]").hide()},hideControls:function(){this.isHiddenControls=!0,this.$refs.container.removeClass("fancybox-show-infobar fancybox-show-toolbar fancybox-show-caption fancybox-show-nav")},showControls:function(){var t=this,e=t.current?t.current.opts:t.opts,n=t.$refs.container;t.isHiddenControls=!1,t.idleSecondsCounter=0,n.toggleClass("fancybox-show-toolbar",!(!e.toolbar||!e.buttons)).toggleClass("fancybox-show-infobar",!!(e.infobar&&t.group.length>1)).toggleClass("fancybox-show-nav",!!(e.arrows&&t.group.length>1)).toggleClass("fancybox-is-modal",!!e.modal),t.$caption?n.addClass("fancybox-show-caption "):n.removeClass("fancybox-show-caption")},toggleControls:function(){this.isHiddenControls?this.showControls():this.hideControls()}}),n.fancybox={version:"3.2.10",defaults:a,getInstance:function(t){var e=n('.fancybox-container:not(".fancybox-is-closing"):last').data("FancyBox"),o=Array.prototype.slice.call(arguments,1);return e instanceof p&&("string"===n.type(t)?e[t].apply(e,o):"function"===n.type(t)&&t.apply(e,o),e)},open:function(t,e,n){return new p(t,e,n)},close:function(t){var e=this.getInstance();e&&(e.close(),t===!0&&this.close())},destroy:function(){this.close(!0),r.off("click.fb-start")},isMobile:e.createTouch!==o&&/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),use3d:function(){var n=e.createElement("div");return t.getComputedStyle&&t.getComputedStyle(n).getPropertyValue("transform")&&!(e.documentMode&&e.documentMode<11)}(),getTranslate:function(t){var e;if(!t||!t.length)return!1;if(e=t.eq(0).css("transform"),e&&e.indexOf("matrix")!==-1?(e=e.split("(")[1],e=e.split(")")[0],e=e.split(",")):e=[],e.length)e=e.length>10?[e[13],e[12],e[0],e[5]]:[e[5],e[4],e[0],e[3]],e=e.map(parseFloat);else{e=[0,0,1,1];var n=/\.*translate\((.*)px,(.*)px\)/i,o=n.exec(t.eq(0).attr("style"));o&&(e[0]=parseFloat(o[2]),e[1]=parseFloat(o[1]))}return{top:e[0],left:e[1],scaleX:e[2],scaleY:e[3],opacity:parseFloat(t.css("opacity")),width:t.width(),height:t.height()}},setTranslate:function(t,e){var n="",i={};if(t&&e)return e.left===o&&e.top===o||(n=(e.left===o?t.position().left:e.left)+"px, "+(e.top===o?t.position().top:e.top)+"px",n=this.use3d?"translate3d("+n+", 0px)":"translate("+n+")"),e.scaleX!==o&&e.scaleY!==o&&(n=(n.length?n+" ":"")+"scale("+e.scaleX+", "+e.scaleY+")"),n.length&&(i.transform=n),e.opacity!==o&&(i.opacity=e.opacity),e.width!==o&&(i.width=e.width),e.height!==o&&(i.height=e.height),t.css(i)},animate:function(t,e,i,a,s){n.isFunction(i)&&(a=i,i=null),n.isPlainObject(e)||t.removeAttr("style"),t.on(d,function(i){(!i||!i.originalEvent||t.is(i.originalEvent.target)&&"z-index"!=i.originalEvent.propertyName)&&(n.fancybox.stop(t),n.isPlainObject(e)?(e.scaleX!==o&&e.scaleY!==o&&(t.css("transition-duration",""),e.width=Math.round(t.width()*e.scaleX),e.height=Math.round(t.height()*e.scaleY),e.scaleX=1,e.scaleY=1,n.fancybox.setTranslate(t,e)),s===!1&&t.removeAttr("style")):s!==!0&&t.removeClass(e),n.isFunction(a)&&a(i))}),n.isNumeric(i)&&t.css("transition-duration",i+"ms"),n.isPlainObject(e)?n.fancybox.setTranslate(t,e):t.addClass(e),e.scaleX&&t.hasClass("fancybox-image-wrap")&&t.parent().addClass("fancybox-is-scaling"),t.data("timer",setTimeout(function(){t.trigger("transitionend")},i+16))},stop:function(t){clearTimeout(t.data("timer")),t.off("transitionend").css("transition-duration",""),t.hasClass("fancybox-image-wrap")&&t.parent().removeClass("fancybox-is-scaling")}},n.fn.fancybox=function(t){var e;return t=t||{},e=t.selector||!1,e?n("body").off("click.fb-start",e).on("click.fb-start",e,{options:t},i):this.off("click.fb-start").on("click.fb-start",{items:this,options:t},i),this},r.on("click.fb-start","[data-fancybox]",i)}}(window,document,window.jQuery||jQuery),function(t){"use strict";var e=function(e,n,o){if(e)return o=o||"","object"===t.type(o)&&(o=t.param(o,!0)),t.each(n,function(t,n){e=e.replace("$"+t,n||"")}),o.length&&(e+=(e.indexOf("?")>0?"&":"?")+o),e},n={youtube:{matcher:/(youtube\.com|youtu\.be|youtube\-nocookie\.com)\/(watch\?(.*&)?v=|v\/|u\/|embed\/?)?(videoseries\?list=(.*)|[\w-]{11}|\?listType=(.*)&list=(.*))(.*)/i,params:{autoplay:1,autohide:1,fs:1,rel:0,hd:1,wmode:"transparent",enablejsapi:1,html5:1},paramPlace:8,type:"iframe",url:"//www.youtube.com/embed/$4",thumb:"//img.youtube.com/vi/$4/hqdefault.jpg" 12 | },vimeo:{matcher:/^.+vimeo.com\/(.*\/)?([\d]+)(.*)?/,params:{autoplay:1,hd:1,show_title:1,show_byline:1,show_portrait:0,fullscreen:1,api:1},paramPlace:3,type:"iframe",url:"//player.vimeo.com/video/$2"},metacafe:{matcher:/metacafe.com\/watch\/(\d+)\/(.*)?/,type:"iframe",url:"//www.metacafe.com/embed/$1/?ap=1"},dailymotion:{matcher:/dailymotion.com\/video\/(.*)\/?(.*)/,params:{additionalInfos:0,autoStart:1},type:"iframe",url:"//www.dailymotion.com/embed/video/$1"},vine:{matcher:/vine.co\/v\/([a-zA-Z0-9\?\=\-]+)/,type:"iframe",url:"//vine.co/v/$1/embed/simple"},instagram:{matcher:/(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i,type:"image",url:"//$1/p/$2/media/?size=l"},gmap_place:{matcher:/(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(((maps\/(place\/(.*)\/)?\@(.*),(\d+.?\d+?)z))|(\?ll=))(.*)?/i,type:"iframe",url:function(t){return"//maps.google."+t[2]+"/?ll="+(t[9]?t[9]+"&z="+Math.floor(t[10])+(t[12]?t[12].replace(/^\//,"&"):""):t[12])+"&output="+(t[12]&&t[12].indexOf("layer=c")>0?"svembed":"embed")}},gmap_search:{matcher:/(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(maps\/search\/)(.*)/i,type:"iframe",url:function(t){return"//maps.google."+t[2]+"/maps?q="+t[5].replace("query=","q=").replace("api=1","")+"&output=embed"}}};t(document).on("objectNeedsType.fb",function(o,i,a){var s,r,c,l,u,d,f,p=a.src||"",h=!1;s=t.extend(!0,{},n,a.opts.media),t.each(s,function(n,o){if(c=p.match(o.matcher)){if(h=o.type,d={},o.paramPlace&&c[o.paramPlace]){u=c[o.paramPlace],"?"==u[0]&&(u=u.substring(1)),u=u.split("&");for(var i=0;ie.clientHeight,a=("scroll"===o||"auto"===o)&&e.scrollWidth>e.clientWidth;return i||a},l=function(t){for(var e=!1;;){if(e=c(t.get(0)))break;if(t=t.parent(),!t.length||t.hasClass("fancybox-stage")||t.is("body"))break}return e},u=function(t){var e=this;e.instance=t,e.$bg=t.$refs.bg,e.$stage=t.$refs.stage,e.$container=t.$refs.container,e.destroy(),e.$container.on("touchstart.fb.touch mousedown.fb.touch",n.proxy(e,"ontouchstart"))};u.prototype.destroy=function(){this.$container.off(".fb.touch")},u.prototype.ontouchstart=function(o){var i=this,c=n(o.target),u=i.instance,d=u.current,f=d.$content,p="touchstart"==o.type;if(p&&i.$container.off("mousedown.fb.touch"),(!o.originalEvent||2!=o.originalEvent.button)&&c.length&&!r(c)&&!r(c.parent())&&(c.is("img")||!(o.originalEvent.clientX>c[0].clientWidth+c.offset().left))){if(!d||i.instance.isAnimating||i.instance.isClosing)return o.stopPropagation(),void o.preventDefault();if(i.realPoints=i.startPoints=a(o),i.startPoints){if(o.stopPropagation(),i.startEvent=o,i.canTap=!0,i.$target=c,i.$content=f,i.opts=d.opts.touch,i.isPanning=!1,i.isSwiping=!1,i.isZooming=!1,i.isScrolling=!1,i.sliderStartPos=i.sliderLastPos||{top:0,left:0},i.contentStartPos=n.fancybox.getTranslate(i.$content),i.contentLastPos=null,i.startTime=(new Date).getTime(),i.distanceX=i.distanceY=i.distance=0,i.canvasWidth=Math.round(d.$slide[0].clientWidth),i.canvasHeight=Math.round(d.$slide[0].clientHeight),n(e).off(".fb.touch").on(p?"touchend.fb.touch touchcancel.fb.touch":"mouseup.fb.touch mouseleave.fb.touch",n.proxy(i,"ontouchend")).on(p?"touchmove.fb.touch":"mousemove.fb.touch",n.proxy(i,"ontouchmove")),n.fancybox.isMobile&&e.addEventListener("scroll",i.onscroll,!0),!i.opts&&!u.canPan()||!c.is(i.$stage)&&!i.$stage.find(c).length)return void(c.is("img")&&o.preventDefault());n.fancybox.isMobile&&(l(c)||l(c.parent()))||o.preventDefault(),1===i.startPoints.length&&("image"===d.type&&(i.contentStartPos.width>i.canvasWidth+1||i.contentStartPos.height>i.canvasHeight+1)?(n.fancybox.stop(i.$content),i.$content.css("transition-duration",""),i.isPanning=!0):i.isSwiping=!0,i.$container.addClass("fancybox-controls--isGrabbing")),2!==i.startPoints.length||u.isAnimating||d.hasError||"image"!==d.type||!d.isLoaded&&!d.$ghost||(i.canTap=!1,i.isSwiping=!1,i.isPanning=!1,i.isZooming=!0,n.fancybox.stop(i.$content),i.$content.css("transition-duration",""),i.centerPointStartX=.5*(i.startPoints[0].x+i.startPoints[1].x)-n(t).scrollLeft(),i.centerPointStartY=.5*(i.startPoints[0].y+i.startPoints[1].y)-n(t).scrollTop(),i.percentageOfImageAtPinchPointX=(i.centerPointStartX-i.contentStartPos.left)/i.contentStartPos.width,i.percentageOfImageAtPinchPointY=(i.centerPointStartY-i.contentStartPos.top)/i.contentStartPos.height,i.startDistanceBetweenFingers=s(i.startPoints[0],i.startPoints[1]))}}},u.prototype.onscroll=function(t){self.isScrolling=!0},u.prototype.ontouchmove=function(t){var e=this,o=n(t.target);return e.isScrolling||!o.is(e.$stage)&&!e.$stage.find(o).length?void(e.canTap=!1):(e.newPoints=a(t),void((e.opts||e.instance.canPan())&&e.newPoints&&e.newPoints.length&&(e.isSwiping&&e.isSwiping===!0||t.preventDefault(),e.distanceX=s(e.newPoints[0],e.startPoints[0],"x"),e.distanceY=s(e.newPoints[0],e.startPoints[0],"y"),e.distance=s(e.newPoints[0],e.startPoints[0]),e.distance>0&&(e.isSwiping?e.onSwipe(t):e.isPanning?e.onPan():e.isZooming&&e.onZoom()))))},u.prototype.onSwipe=function(e){var a,s=this,r=s.isSwiping,c=s.sliderStartPos.left||0;if(r!==!0)"x"==r&&(s.distanceX>0&&(s.instance.group.length<2||0===s.instance.current.index&&!s.instance.current.opts.loop)?c+=Math.pow(s.distanceX,.8):s.distanceX<0&&(s.instance.group.length<2||s.instance.current.index===s.instance.group.length-1&&!s.instance.current.opts.loop)?c-=Math.pow(-s.distanceX,.8):c+=s.distanceX),s.sliderLastPos={top:"x"==r?0:s.sliderStartPos.top+s.distanceY,left:c},s.requestId&&(i(s.requestId),s.requestId=null),s.requestId=o(function(){s.sliderLastPos&&(n.each(s.instance.slides,function(t,e){var o=e.pos-s.instance.currPos;n.fancybox.setTranslate(e.$slide,{top:s.sliderLastPos.top,left:s.sliderLastPos.left+o*s.canvasWidth+o*e.opts.gutter})}),s.$container.addClass("fancybox-is-sliding"))});else if(Math.abs(s.distance)>10){if(s.canTap=!1,s.instance.group.length<2&&s.opts.vertical?s.isSwiping="y":s.instance.isDragging||s.opts.vertical===!1||"auto"===s.opts.vertical&&n(t).width()>800?s.isSwiping="x":(a=Math.abs(180*Math.atan2(s.distanceY,s.distanceX)/Math.PI),s.isSwiping=a>45&&a<135?"y":"x"),s.canTap=!1,"y"===s.isSwiping&&n.fancybox.isMobile&&(l(s.$target)||l(s.$target.parent())))return void(s.isScrolling=!0);s.instance.isDragging=s.isSwiping,s.startPoints=s.newPoints,n.each(s.instance.slides,function(t,e){n.fancybox.stop(e.$slide),e.$slide.css("transition-duration",""),e.inTransition=!1,e.pos===s.instance.current.pos&&(s.sliderStartPos.left=n.fancybox.getTranslate(e.$slide).left)}),s.instance.SlideShow&&s.instance.SlideShow.isActive&&s.instance.SlideShow.stop()}},u.prototype.onPan=function(){var t=this;return s(t.newPoints[0],t.realPoints[0])<(n.fancybox.isMobile?10:5)?void(t.startPoints=t.newPoints):(t.canTap=!1,t.contentLastPos=t.limitMovement(),t.requestId&&(i(t.requestId),t.requestId=null),void(t.requestId=o(function(){n.fancybox.setTranslate(t.$content,t.contentLastPos)})))},u.prototype.limitMovement=function(){var t,e,n,o,i,a,s=this,r=s.canvasWidth,c=s.canvasHeight,l=s.distanceX,u=s.distanceY,d=s.contentStartPos,f=d.left,p=d.top,h=d.width,g=d.height;return i=h>r?f+l:f,a=p+u,t=Math.max(0,.5*r-.5*h),e=Math.max(0,.5*c-.5*g),n=Math.min(r-h,.5*r-.5*h),o=Math.min(c-g,.5*c-.5*g),h>r&&(l>0&&i>t&&(i=t-1+Math.pow(-t+f+l,.8)||0),l<0&&ic&&(u>0&&a>e&&(a=e-1+Math.pow(-e+p+u,.8)||0),u<0&&aa?(t=t>0?0:t,t=ts?(e=e>0?0:e,e=e50?(n.fancybox.animate(o.instance.current.$slide,{top:o.sliderStartPos.top+o.distanceY+150*o.velocityY,opacity:0},150),i=o.instance.close(!0,300)):"x"==t&&o.distanceX>50&&a>1?i=o.instance.previous(o.speedX):"x"==t&&o.distanceX<-50&&a>1&&(i=o.instance.next(o.speedX)),i!==!1||"x"!=t&&"y"!=t||(e||a<2?o.instance.centerSlide(o.instance.current,150):o.instance.jumpTo(o.instance.current.index)),o.$container.removeClass("fancybox-is-sliding")},u.prototype.endPanning=function(){var t,e,o,i=this;i.contentLastPos&&(i.opts.momentum===!1?(t=i.contentLastPos.left,e=i.contentLastPos.top):(t=i.contentLastPos.left+i.velocityX*i.speed,e=i.contentLastPos.top+i.velocityY*i.speed),o=i.limitPosition(t,e,i.contentStartPos.width,i.contentStartPos.height),o.width=i.contentStartPos.width,o.height=i.contentStartPos.height,n.fancybox.animate(i.$content,o,330))},u.prototype.endZooming=function(){var t,e,o,i,a=this,s=a.instance.current,r=a.newWidth,c=a.newHeight;a.contentLastPos&&(t=a.contentLastPos.left,e=a.contentLastPos.top,i={top:e,left:t,width:r,height:c,scaleX:1,scaleY:1},n.fancybox.setTranslate(a.$content,i),rs.width||c>s.height?a.instance.scaleToActual(a.centerPointStartX,a.centerPointStartY,150):(o=a.limitPosition(t,e,r,c),n.fancybox.setTranslate(a.content,n.fancybox.getTranslate(a.$content)),n.fancybox.animate(a.$content,o,150)))},u.prototype.onTap=function(t){var e,o=this,i=n(t.target),s=o.instance,r=s.current,c=t&&a(t)||o.startPoints,l=c[0]?c[0].x-o.$stage.offset().left:0,u=c[0]?c[0].y-o.$stage.offset().top:0,d=function(e){var i=r.opts[e];if(n.isFunction(i)&&(i=i.apply(s,[r,t])),i)switch(i){case"close":s.close(o.startEvent);break;case"toggleControls":s.toggleControls(!0);break;case"next":s.next();break;case"nextOrClose":s.group.length>1?s.next():s.close(o.startEvent);break;case"zoom":"image"==r.type&&(r.isLoaded||r.$ghost)&&(s.canPan()?s.scaleToFit():s.isScaledDown()?s.scaleToActual(l,u):s.group.length<2&&s.close(o.startEvent))}};if((!t.originalEvent||2!=t.originalEvent.button)&&(i.is("img")||!(l>i[0].clientWidth+i.offset().left))){if(i.is(".fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container"))e="Outside";else if(i.is(".fancybox-slide"))e="Slide";else{if(!s.current.$content||!s.current.$content.find(i).addBack().filter(i).length)return;e="Content"}if(o.tapped){if(clearTimeout(o.tapped),o.tapped=null,Math.abs(l-o.tapX)>50||Math.abs(u-o.tapY)>50)return this;d("dblclick"+e)}else o.tapX=l,o.tapY=u,r.opts["dblclick"+e]&&r.opts["dblclick"+e]!==r.opts["click"+e]?o.tapped=setTimeout(function(){o.tapped=null,d("click"+e)},500):d("click"+e);return this}},n(e).on("onActivate.fb",function(t,e){e&&!e.Guestures&&(e.Guestures=new u(e))})}(window,document,window.jQuery||jQuery),function(t,e){"use strict";e.extend(!0,e.fancybox.defaults,{btnTpl:{slideShow:''},slideShow:{autoStart:!1,speed:3e3}});var n=function(t){this.instance=t,this.init()};e.extend(n.prototype,{timer:null,isActive:!1,$button:null,init:function(){var t=this;t.$button=t.instance.$refs.toolbar.find("[data-fancybox-play]").on("click",function(){t.toggle()}),(t.instance.group.length<2||!t.instance.group[t.instance.currIndex].opts.slideShow)&&t.$button.hide()},set:function(t){var e=this;e.instance&&e.instance.current&&(t===!0||e.instance.current.opts.loop||e.instance.currIndex'},fullScreen:{autoStart:!1}}),e(t).on({"onInit.fb":function(t,e){var n;e&&e.group[e.currIndex].opts.fullScreen?(n=e.$refs.container,n.on("click.fb-fullscreen","[data-fancybox-fullscreen]",function(t){t.stopPropagation(),t.preventDefault(),o.toggle(n[0])}),e.opts.fullScreen&&e.opts.fullScreen.autoStart===!0&&o.request(n[0]),e.FullScreen=o):e&&e.$refs.toolbar.find("[data-fancybox-fullscreen]").hide()},"afterKeydown.fb":function(t,e,n,o,i){e&&e.FullScreen&&70===i&&(o.preventDefault(),e.FullScreen.toggle(e.$refs.container[0]))},"beforeClose.fb":function(t){t&&t.FullScreen&&o.exit()}}),e(t).on(n.fullscreenchange,function(){var t=o.isFullscreen(),n=e.fancybox.getInstance();n&&(n.current&&"image"===n.current.type&&n.isAnimating&&(n.current.$content.css("transition","none"),n.isAnimating=!1,n.update(!0,!0,0)),n.trigger("onFullscreenChange",t),n.$refs.container.toggleClass("fancybox-is-fullscreen",t))})}(document,window.jQuery||jQuery),function(t,e){"use strict";e.fancybox.defaults=e.extend(!0,{btnTpl:{thumbs:''},thumbs:{autoStart:!1,hideOnClose:!0,parentEl:".fancybox-container",axis:"y"}},e.fancybox.defaults);var n=function(t){this.init(t)};e.extend(n.prototype,{$button:null,$grid:null,$list:null,isVisible:!1,isActive:!1,init:function(t){var e=this;e.instance=t,t.Thumbs=e;var n=t.group[0],o=t.group[1];e.opts=t.group[t.currIndex].opts.thumbs,e.$button=t.$refs.toolbar.find("[data-fancybox-thumbs]"),e.opts&&n&&o&&("image"==n.type||n.opts.thumb||n.opts.$thumb)&&("image"==o.type||o.opts.thumb||o.opts.$thumb)?(e.$button.show().on("click",function(){e.toggle()}),e.isActive=!0):e.$button.hide()},create:function(){var t,n,o=this,i=o.instance,a=o.opts.parentEl;o.$grid=e('
    ').appendTo(i.$refs.container.find(a).addBack().filter(a)),t="
      ",e.each(i.group,function(e,o){n=o.opts.thumb||(o.opts.$thumb?o.opts.$thumb.attr("src"):null),n||"image"!==o.type||(n=o.src),n&&n.length&&(t+='
    • ')}),t+="
    ",o.$list=e(t).appendTo(o.$grid).on("click","li",function(){i.jumpTo(e(this).data("index"))}),o.$list.find("img").hide().one("load",function(){var t,n,o,i,a=e(this).parent().removeClass("fancybox-thumbs-loading"),s=a.outerWidth(),r=a.outerHeight();t=this.naturalWidth||this.width,n=this.naturalHeight||this.height,o=t/s,i=n/r,o>=1&&i>=1&&(o>i?(t/=i,n=r):(t=s,n/=o)),e(this).css({width:Math.floor(t),height:Math.floor(n),"margin-top":n>r?Math.floor(.3*r-.3*n):Math.floor(.5*r-.5*n),"margin-left":Math.floor(.5*s-.5*t)}).show()}).each(function(){this.src=e(this).data("src")}),"x"===o.opts.axis&&o.$list.width(parseInt(o.$grid.css("padding-right"))+i.group.length*o.$list.children().eq(0).outerWidth(!0)+"px")},focus:function(t){var e,n,o=this,i=o.$list;o.instance.current&&(e=i.children().removeClass("fancybox-thumbs-active").filter('[data-index="'+o.instance.current.index+'"]').addClass("fancybox-thumbs-active"),n=e.position(),"y"===o.opts.axis&&(n.top<0||n.top>i.height()-e.outerHeight())?i.stop().animate({scrollTop:i.scrollTop()+n.top},t):"x"===o.opts.axis&&(n.lefti.parent().scrollLeft()+(i.parent().width()-e.outerWidth()))&&i.parent().stop().animate({scrollLeft:n.left},t))},update:function(){this.instance.$refs.container.toggleClass("fancybox-show-thumbs",this.isVisible),this.isVisible?(this.$grid||this.create(),this.instance.trigger("onThumbsShow"),this.focus(0)):this.$grid&&this.instance.trigger("onThumbsHide"),this.instance.update()},hide:function(){this.isVisible=!1,this.update()},show:function(){this.isVisible=!0,this.update()},toggle:function(){this.isVisible=!this.isVisible,this.update()}}),e(t).on({"onInit.fb":function(t,e){var o;e&&!e.Thumbs&&(o=new n(e),o.isActive&&o.opts.autoStart===!0&&o.show())},"beforeShow.fb":function(t,e,n,o){var i=e&&e.Thumbs;i&&i.isVisible&&i.focus(o?0:250)},"afterKeydown.fb":function(t,e,n,o,i){var a=e&&e.Thumbs;a&&a.isActive&&71===i&&(o.preventDefault(),a.toggle())},"beforeClose.fb":function(t,e){var n=e&&e.Thumbs;n&&n.isVisible&&n.opts.hideOnClose!==!1&&n.$grid.hide()}})}(document,window.jQuery),function(t,e){"use strict";function n(t){var e={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};return String(t).replace(/[&<>"'`=\/]/g,function(t){return e[t]})}e.extend(!0,e.fancybox.defaults,{btnTpl:{share:''},share:{tpl:''}}),e(t).on("click","[data-fancybox-share]",function(){var t,o,i=e.fancybox.getInstance();i&&(t=i.current.opts.hash===!1?i.current.src:window.location,o=i.current.opts.share.tpl.replace(/\{\{media\}\}/g,"image"===i.current.type?encodeURIComponent(i.current.src):"").replace(/\{\{url\}\}/g,encodeURIComponent(t)).replace(/\{\{url_raw\}\}/g,n(t)).replace(/\{\{descr\}\}/g,i.$caption?encodeURIComponent(i.$caption.text()):""),e.fancybox.open({src:i.translate(i,o),type:"html",opts:{animationEffect:"fade",animationDuration:250,afterLoad:function(t,e){e.$content.find(".fancybox-share__links a").click(function(){return window.open(this.href,"Share","width=550, height=450"),!1})}}}))})}(document,window.jQuery||jQuery),function(t,e,n){"use strict";function o(){var t=e.location.hash.substr(1),n=t.split("-"),o=n.length>1&&/^\+?\d+$/.test(n[n.length-1])?parseInt(n.pop(-1),10)||1:1,i=n.join("-");return o<1&&(o=1),{hash:t,index:o,gallery:i}}function i(t){var e;""!==t.gallery&&(e=n("[data-fancybox='"+n.escapeSelector(t.gallery)+"']").eq(t.index-1),e.length||(e=n("#"+n.escapeSelector(t.gallery))),e.length&&(s=!1,e.trigger("click")))}function a(t){var e;return!!t&&(e=t.current?t.current.opts:t.opts,e.hash||(e.$orig?e.$orig.data("fancybox"):""))}n.escapeSelector||(n.escapeSelector=function(t){var e=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g,n=function(t,e){return e?"\0"===t?"�":t.slice(0,-1)+"\\"+t.charCodeAt(t.length-1).toString(16)+" ":"\\"+t};return(t+"").replace(e,n)});var s=!0,r=null,c=null;n(function(){n.fancybox.defaults.hash!==!1&&(n(t).on({"onInit.fb":function(t,e){var n,i;e.group[e.currIndex].opts.hash!==!1&&(n=o(),i=a(e),i&&n.gallery&&i==n.gallery&&(e.currIndex=n.index-1))},"beforeShow.fb":function(n,o,i){var l;i&&i.opts.hash!==!1&&(l=a(o),l&&""!==l&&(e.location.hash.indexOf(l)<0&&(o.opts.origHash=e.location.hash),r=l+(o.group.length>1?"-"+(i.index+1):""),"replaceState"in e.history?(c&&clearTimeout(c),c=setTimeout(function(){e.history[s?"pushState":"replaceState"]({},t.title,e.location.pathname+e.location.search+"#"+r),c=null,s=!1},300)):e.location.hash=r))},"beforeClose.fb":function(o,i,s){var l,u;c&&clearTimeout(c),s.opts.hash!==!1&&(l=a(i),u=i&&i.opts.origHash?i.opts.origHash:"",l&&""!==l&&("replaceState"in history?e.history.replaceState({},t.title,e.location.pathname+e.location.search+u):(e.location.hash=u,n(e).scrollTop(i.scrollTop).scrollLeft(i.scrollLeft))),r=null)}}),n(e).on("hashchange.fb",function(){var t=o();n.fancybox.getInstance()?!r||r===t.gallery+"-"+t.index||1===t.index&&r==t.gallery||(r=null,n.fancybox.close()):""!==t.gallery&&i(t)}),setTimeout(function(){i(o())},50))})}(document,window,window.jQuery||jQuery),function(t,e){"use strict";var n=(new Date).getTime();e(t).on({"onInit.fb":function(t,e,o){e.$refs.stage.on("mousewheel DOMMouseScroll wheel MozMousePixelScroll",function(t){var o=e.current,i=(new Date).getTime();e.group.length<1||o.opts.wheel===!1||"auto"===o.opts.wheel&&"image"!==o.type||(t.preventDefault(),t.stopPropagation(),o.$slide.hasClass("fancybox-animated")||(t=t.originalEvent||t,i-n<250||(n=i,e[(-t.deltaY||-t.deltaX||t.wheelDelta||-t.detail)<0?"next":"previous"]())))})}})}(document,window.jQuery||jQuery); --------------------------------------------------------------------------------