├── LICENSE ├── README.md ├── TODO.md ├── htracr.js ├── lib ├── asset │ ├── g.raphael-min.js │ ├── htracr-comm.js │ ├── htracr-data.js │ ├── htracr-ui.js │ ├── htracr.css │ ├── htracr.html │ ├── jquery-min.js │ ├── jquery-ui-min.js │ ├── jquery-ui.css │ ├── raphael-min.js │ ├── ui-bg_glass_20_555555_1x400.png │ ├── ui-bg_glass_40_0078a3_1x400.png │ ├── ui-bg_inset-soft_25_000000_1x100.png │ ├── ui-bg_inset-soft_30_f58400_1x100.png │ ├── ui-icons_cccccc_256x240.png │ └── zoom.js ├── index.js └── server.js └── package.json /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2011 Mark Nottingham 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # htracr 3 | 4 | htracr is a packet sniffer and visualisation tool for HTTP. It does not give 5 | you a score, grade, or hold your hand when you're crying because your site 6 | is so damn slow, but it will give you unparalleled insight into what's 7 | actually happening on the wire between your browser and the Web. 8 | 9 | 10 | ## Installing htracr 11 | 12 | First you'll need [Node](http://nodejs.org/) and its package manager, 13 | [npm](http://npmjs.org/). You'll also need a modern Web browser 14 | (known to work: Safari 5, FireFox 4, and Chrome). 15 | 16 | Then, htracr can be installed with npm like this: 17 | 18 | > sudo npm -g install htracr 19 | 20 | which will install dependencies automatically. 21 | 22 | See 'Installation Problems?' below if you have any issues getting htracr 23 | onto your system. 24 | 25 | Under the covers, htracr relies upon 26 | [node_pcap](https://github.com/mranney/node_pcap/), 27 | [Raphael](http://raphaeljs.com/), [JQuery](http://jquery.com/), 28 | [optimist](https://github.com/substack/node-optimist), and 29 | [formidable](https://github.com/felixge/node-formidable). 30 | 31 | 32 | ## Using htracr 33 | 34 | htracr is designed for use on the same machine your web browser or other 35 | client runs on; while it's possible to run it on a server, it'll be difficult 36 | to make sense of all of the traffic coming to a normal server. 37 | 38 | To use htracr, start it up like this: 39 | 40 | > htracr [listen-port] 41 | 42 | where _listen_port_ is the port you'd like htracr to be available on. Then, 43 | point your browser at it; e.g.: 44 | 45 | > htracr 8000 46 | 47 | means you should point at: 48 | 49 | > http://localhost:8000/ 50 | 51 | On some operating systems, you may need to specify the interface to listen 52 | on. For example: 53 | 54 | > htracr 8000 eth0 55 | 56 | and in some cases, you may need permission to listen to the device, making 57 | the appropriate command line something like: 58 | 59 | > sudo htracr 8000 eth0 60 | 61 | Then, press 'start' to start capturing HTTP traffic, and 'stop' to show it. 62 | Currently, htracr only captures traffic on port 80. 63 | 64 | The slider will adjust the time scale. You can use the keyboard arrows 65 | to navigate between packets and HTTP messages. 66 | 67 | 68 | ## Installation Problems? 69 | 70 | ### libpcap 71 | 72 | If npm complains about problems with pcap, like this: 73 | 74 | npm ERR! Failed at the pcap@0.2.7 install script. 75 | 76 | it usually means that it couldn't find libpcap when building. See the 77 | instructions here: . 78 | 79 | On my OSX machine, I have to build like this (becoming root first): 80 | 81 | > CXXFLAGS=-I/opt/local/include npm -g install htracr 82 | 83 | because my pcap headers are in a non-standard place (thanks to MacPorts). 84 | YMMV. 85 | 86 | ### npm 87 | 88 | Older versions of npm interact strangely with optimist and htracr. If you 89 | have other issues installing npm, try upgrading npm, then re-installing 90 | htracr. 91 | 92 | 93 | ## Contact 94 | 95 | Mark Nottingham 96 | 97 | http://github.com/mnot/htracr/ 98 | 99 | 100 | ## Obligatory Screenshot 101 | 102 | ![htracr screenshot](https://mnot.github.io/htracr/htracr.png) 103 | 104 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ### TCP visualisation 2 | 3 | - Make connection states visible: 4 | - handshaking 5 | - connected 6 | - half-connected 7 | - idle 8 | - disconnecting 9 | - buffer-full? 10 | - per-connection stats: 11 | - total packets (graph of types?) 12 | - congestion window (over time?) 13 | - receive window (over time?) 14 | - rtt calculation / visualisation 15 | - handle tcp-retransmit / tcp-reset 16 | - show ack relationships? 17 | 18 | ### HTTP visualisation 19 | 20 | - relate requests to responses (for navigation / referer linking) 21 | - message stats 22 | - message delay 23 | - number of round trips 24 | - highlight unusual methods / status codes 25 | - click on response to open window with it 26 | - click on request to re-make request 27 | - server stall time (based upon rtt / packet sizes / psh) 28 | 29 | ### Misc. Features 30 | 31 | - help button (modal) 32 | - configurable sniff port(s) in-browser 33 | - printing 34 | - trace DNS 35 | - show scale in round trips 36 | - per-server stats 37 | - dump pcap sessions (requires support in node_pcap) 38 | - magnifier, because packets get lost between the pixels 39 | 40 | ### UI Tweaks 41 | 42 | - allow removing connections / servers, or focus on one 43 | - improved keyboard controls 44 | - allow copying from msg (e.g., url ) 45 | - make highlighting more prominent (e.g., pulse animation?) 46 | 47 | ### Bugs 48 | 49 | - header names are case-sensitive 50 | - proper handling for location headers (e.g., relative) 51 | - need HTTP status phrase 52 | - some requests not drawn in firefox 3? 53 | - keep centre on zoom 54 | - IE capture caching 55 | -------------------------------------------------------------------------------- /htracr.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var argv = require('optimist').argv 4 | var dns = require('dns') 5 | var fs = require('fs') 6 | var node_http = require('http') 7 | var node_url = require('url') 8 | var pcap = require("pcap") 9 | var server = require('htracr').server 10 | var util = require('util') 11 | 12 | 13 | var htracr = { 14 | device: '', 15 | sniff_port: 80, 16 | packets: [], 17 | capture: {sessions: {}}, // [server_ip][local_port] = {...} 18 | captured_packets: 0, 19 | msgs: {}, 20 | server_names: {}, 21 | pcap_session: undefined, 22 | drop_watcher: undefined, 23 | err: undefined, 24 | 25 | clear: function () { 26 | var self = this 27 | self.packets = [] 28 | self.capture = {sessions: {}} 29 | self.captured_packets = 0 30 | self.msgs = {} 31 | self.err = undefined 32 | if (self.drop_watcher) { 33 | clearInterval(self.drop_watcher) 34 | } 35 | self.drop_watcher = undefined 36 | }, 37 | 38 | start_capture: function() { 39 | var self = this 40 | self.clear() 41 | var f = "tcp port " + self.sniff_port 42 | var b = 10 43 | // FIXME: where did error catch go? 44 | self.capture.start = new Date().getTime() 45 | self.pcap_session = pcap.createSession(self.device, f, (b * 1024 * 1024)) 46 | this.setup_listeners() 47 | console.log("Sniffing on " + self.pcap_session.device_name) 48 | 49 | // Check for pcap dropped packets on an interval 50 | self.drop_watcher = setInterval(function () { 51 | var stats = self.pcap_session.stats() 52 | if (stats.ps_drop > 0) { 53 | // TODO: notify browser through err as well 54 | console.log( 55 | "dropped packets, need larger buffer or less work to do: " 56 | + util.inspect(stats) 57 | ) 58 | } 59 | }, 2000) 60 | }, 61 | 62 | stop_capture: function () { 63 | var self = this 64 | if (self.pcap_session == undefined) { 65 | return 66 | } 67 | if (self.drop_watcher) { 68 | clearInterval(self.drop_watcher) 69 | } 70 | self.drop_watcher == undefined 71 | self.capture.end = new Date().getTime() 72 | self.pcap_session.close() 73 | self.pcap_session = undefined 74 | console.log("Stopped sniffing") 75 | }, 76 | 77 | load_file: function(filename) { 78 | var self = this 79 | var f = "tcp port " + self.sniff_port 80 | console.log("Processing upload file: " + filename) 81 | self.pcap_session = pcap.createOfflineSession(filename, f) 82 | self.setup_listeners() 83 | }, 84 | 85 | setup_listeners: function () { 86 | var self = this 87 | var tcp_tracker = new pcap.TCP_tracker() 88 | 89 | // listen for packets, decode them, and feed TCP to the tracker 90 | self.pcap_session.on('packet', function (raw_packet) { 91 | self.captured_packets += 1 92 | var packet = pcap.decode.packet(raw_packet) 93 | self.save_packet(packet) 94 | // This needs to happen AFTER we note the packet. 95 | tcp_tracker.track_packet(packet) 96 | }) 97 | 98 | tcp_tracker.on("start", function (session) { 99 | var conn = self.get_conn(session) 100 | conn.start = session.current_cap_time 101 | }) 102 | 103 | tcp_tracker.on("retransmit", function (session, direction, seqno) { 104 | var conn = self.get_conn(session) 105 | conn.events.push( 106 | self.format_event(session, 'retransmit', { 107 | 'direction': direction, 108 | 'seqno': seqno 109 | }) 110 | ) 111 | }) 112 | 113 | tcp_tracker.on("reset", function (session) { 114 | // Right now, it's only from dst. 115 | var conn = self.get_conn(session) 116 | conn.events.push(self.format_event(session, 'reset')) 117 | }) 118 | 119 | tcp_tracker.on("syn retry", function (session) { 120 | var conn = self.get_conn(session) 121 | conn.events.push(self.format_event(session, 'retry')) 122 | }) 123 | 124 | tcp_tracker.on("end", function (session) { 125 | var conn = self.get_conn(session) 126 | conn.end = session.current_cap_time 127 | var last_req = self.get_last(conn.http_reqs) 128 | if (last_req && ! last_req.end) { 129 | last_req.end = conn.end 130 | last_req.end_packet = conn.packets.length - 1 131 | self.msg_stats(last_req, conn) 132 | } 133 | var last_res = self.get_last(conn.http_ress) 134 | if (last_res && ! last_res.end) { 135 | last_res.end = conn.end 136 | last_res.end_packet = conn.packets.length - 1 137 | self.msg_stats(last_res, conn) 138 | } 139 | }) 140 | 141 | 142 | tcp_tracker.on('http request', function (session, http) { 143 | var conn = self.get_conn(session) 144 | var request = self.clone(http.request) 145 | var corrected = self.rewind_packets(conn.packets, 'out', request) 146 | conn.http_reqs.push({ 147 | 'kind': 'req', 148 | 'start': corrected.start, 149 | 'start_packet': corrected.index, 150 | 'data': request 151 | }) 152 | }) 153 | 154 | tcp_tracker.on('http request body', function (session, http, data) { 155 | }) 156 | 157 | tcp_tracker.on('http request complete', function (session, http) { 158 | var conn = self.get_conn(session) 159 | var req = self.get_last(conn.http_reqs) 160 | req.end = session.current_cap_time 161 | req.end_packet = conn.packets.length - 1 162 | self.msg_stats(req, conn, conn.http_reqs.length) 163 | }) 164 | 165 | tcp_tracker.on('http response', function (session, http) { 166 | var conn = self.get_conn(session) 167 | var response = self.clone(http.response) 168 | var corrected = self.rewind_packets(conn.packets, 'in', response) 169 | conn.http_ress.push({ 170 | 'kind': 'res', 171 | 'start': corrected.start, 172 | 'start_packet': corrected.index, 173 | 'data': response 174 | }) 175 | }) 176 | 177 | tcp_tracker.on('http response body', function (session, http, data) { 178 | }) 179 | 180 | tcp_tracker.on('http response complete', function (session, http) { 181 | var conn = self.get_conn(session) 182 | var res = self.get_last(conn.http_ress) 183 | res.end = session.current_cap_time 184 | res.end_packet = conn.packets.length - 1 185 | self.msg_stats(res, conn, conn.http_ress.length) 186 | }) 187 | 188 | tcp_tracker.on('http error', function (session, direction, error) { 189 | console.log(" HTTP parser error: " + error) 190 | // TODO - probably need to force this transaction to be over 191 | }) 192 | }, 193 | 194 | // search backwards through a list of packets and find where a http message 195 | // really started. This is imprecise, but should be OK most of the time. 196 | // see: https://github.com/mranney/node_pcap/issues#issue/8 197 | rewind_packets: function (packets, interesting_dir, msg) { 198 | var bytes = [ 199 | msg.method || "", 200 | msg.status_code || "", // don't have access to phrase :( 201 | " ", 202 | msg.url || "", 203 | " ", 204 | "HTTP/1.x", // works out the same for request or response 205 | "\n" 206 | ] 207 | console.log(bytes) 208 | for (var h in msg.headers) { 209 | if (msg.headers.hasOwnProperty(h)) { 210 | bytes.push(h + ": " + msg.headers[h] + "\n") // conservative - no \r 211 | } 212 | } 213 | bytes.push("\n") // conservative - no \r 214 | var num_bytes = bytes.join("").length 215 | 216 | var bytes_seen = 0 217 | for (var i = packets.length - 1; i >= 0; i -= 1) { 218 | var packet = packets[i] 219 | if (packet.dir == interesting_dir) { 220 | bytes_seen += packet.data_sz 221 | } 222 | if (bytes_seen >= num_bytes) { 223 | return { 224 | start: packet.time, 225 | index: i 226 | } 227 | } 228 | } 229 | // Shouldn't get here... 230 | console.log("Couldn't find the start of message: " + msg); 231 | return { 232 | start: msg.time, 233 | index: packets.length 234 | }; 235 | }, 236 | 237 | // compute stats for the given HTTP message 238 | msg_stats: function (msg, conn, msg_offset) { 239 | var target_dir = msg.kind == 'req' ? 'out' : 'in' 240 | var packet_count = 0 241 | var byte_count = 0 242 | for (var i = msg.start_packet; i <= msg.end_packet; i += 1) { 243 | var packet = conn.packets[i] 244 | if (packet.data_sz > 0 && packet.dir === target_dir) { 245 | packet_count += 1 246 | byte_count += packet.data_sz 247 | packet.msg = msg_offset - 1 248 | } 249 | } 250 | msg.data_packet_count = packet_count 251 | msg.packet_byte_count = byte_count 252 | }, 253 | 254 | // return a data structure for an event 255 | format_event: function (session, event_kind, data) { 256 | var out_data = data || {} 257 | out_data.time = session.current_cap_time 258 | out_data.kind = event_kind 259 | return out_data 260 | }, 261 | 262 | // given a packet, save its data in self.packets and info in self.capture 263 | save_packet: function (packet) { 264 | var self = this 265 | var direction 266 | var server 267 | var local_port 268 | 269 | // fake in start and end for loaded sessions 270 | if (! self.capture.start) { 271 | self.capture.start = packet.pcap_header.time_ms 272 | } 273 | if (! self.capture.end || self.capture.end < packet.pcap_header.time_ms) { 274 | self.capture.end = packet.pcap_header.time_ms 275 | } 276 | 277 | var ipname = ('ipv6' in packet.link) ? 'ipv6' : 'ip', 278 | packetTcp = packet.link[ipname].tcp 279 | 280 | if (packetTcp.dport == 80) { 281 | server = packet.link[ipname].daddr 282 | local_port = packetTcp.sport 283 | direction = "out" 284 | } else { 285 | server = packet.link[ipname].saddr 286 | local_port = packetTcp.dport 287 | direction = "in" 288 | } 289 | 290 | // reverse lookup 291 | if (self.server_names[server] == undefined) { 292 | self.server_names[server] = "" 293 | dns.reverse(server, function(err, domains) { 294 | if (! err) { 295 | self.server_names[server] = domains[0] 296 | } else { 297 | delete self.server_names[server] 298 | } 299 | }) 300 | } 301 | 302 | var detail = { 303 | time: packet.pcap_header.time_ms, 304 | ws: packetTcp.window_size, 305 | dir: direction, 306 | flags: packetTcp.flags, 307 | options: packetTcp.options, 308 | data_sz: packetTcp.data_bytes, 309 | packet_id: self.packets.length 310 | } 311 | 312 | if (detail.data_sz == 0 && 313 | detail.flags.ack && 314 | ! detail.flags.syn && 315 | ! detail.flags.rst && 316 | ! detail.flags.fin && 317 | ! detail.flags.psh 318 | ) { 319 | detail.ack_only = true; 320 | } else { 321 | detail.ack_only = false; 322 | } 323 | 324 | self._get_conn(server, local_port).packets.push(detail) 325 | self.packets.push((packetTcp.data || "").toString('utf8')) 326 | }, 327 | 328 | // given a TCP session, return the relevant data structure in self.capture 329 | get_conn: function (tcp_session) { 330 | var self = this 331 | var server 332 | var local_port 333 | if (tcp_session.dst.split(":")[1] == self.sniff_port) { 334 | server = tcp_session.dst.split(":")[0] 335 | local_port = tcp_session.src.split(":")[1] 336 | } else { 337 | server = tcp_session.src.split(":")[0] 338 | local_port = tcp_session.dst.split(":")[1] 339 | } 340 | return self._get_conn(server, local_port) 341 | }, 342 | 343 | // given a server and local_port, return the relevant data structure 344 | _get_conn: function (server, local_port) { 345 | if (this.capture.sessions[server] == undefined) { 346 | this.capture.sessions[server] = {} 347 | } 348 | var server_conn = this.capture.sessions[server] 349 | if (server_conn[local_port] == undefined) { 350 | server_conn[local_port] = { 351 | 'server': server, 352 | 'local_port': local_port, 353 | 'events': [], 354 | 'packets': [], 355 | 'http_reqs': [], 356 | 'http_ress': [] 357 | } 358 | } 359 | return server_conn[local_port] 360 | }, 361 | 362 | get_last: function (arr) { 363 | return arr[arr.length - 1] 364 | }, 365 | 366 | clone: function (obj) { 367 | if (null == obj || "object" != typeof obj) { 368 | console.log("Cloning a non-object.") 369 | return obj 370 | } 371 | var copy = obj.constructor() 372 | for (var attr in obj) { 373 | if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr] 374 | } 375 | return copy 376 | } 377 | } 378 | 379 | 380 | // port to listen to 381 | var port = parseInt(argv._[0], 10) 382 | if (! port || port == NaN) { 383 | console.log("Usage: htracr listen-port [device]") 384 | process.exit(1) 385 | } 386 | 387 | // device to snoop on 388 | var device = argv._[1] 389 | if (device) { 390 | htracr.device = device 391 | } 392 | 393 | server.start(port, htracr) 394 | -------------------------------------------------------------------------------- /lib/asset/g.raphael-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * g.Raphael 0.4.1 - Charting library, based on Raphaël 3 | * 4 | * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com) 5 | * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. 6 | */ 7 | (function(){var a=Math.max,c=Math.min;Raphael.fn.g=Raphael.fn.g||{};Raphael.fn.g.markers={disc:"disc",o:"disc",flower:"flower",f:"flower",diamond:"diamond",d:"diamond",square:"square",s:"square",triangle:"triangle",t:"triangle",star:"star","*":"star",cross:"cross",x:"cross",plus:"plus","+":"plus",arrow:"arrow","->":"arrow"};Raphael.fn.g.shim={stroke:"none",fill:"#000","fill-opacity":0};Raphael.fn.g.txtattr={font:"12px Arial, sans-serif"};Raphael.fn.g.colors=[];var e=[0.6,0.2,0.05,0.1333,0.75,0];for(var b=0;b<10;b++){if(b=i*2){this[0].attr({path:["M",f,m+i,"a",i,i,0,1,1,0,-i*2,i,i,0,1,1,0,i*2,"m",0,-i*2-j,"a",i+j,i+j,0,1,0,0,(i+j)*2,"L",f+i+j,m+o.height/2+j,"l",o.width+2*j,0,0,-o.height-2*j,-o.width-2*j,0,"L",f,m-i-j].join(",")});}else{var n=Math.sqrt(Math.pow(i+j,2)-Math.pow(o.height/2+j,2));this[0].attr({path:["M",f,m+i,"c",-h,0,-i,h-i,-i,-i,0,-h,i-h,-i,i,-i,h,0,i,i-h,i,i,0,h,h-i,i,-i,i,"M",f+n,m-o.height/2-j,"a",i+j,i+j,0,1,0,0,o.height+2*j,"l",i+j-n+o.width+2*j,0,0,-o.height-2*j,"L",f+n,m-o.height/2-j].join(",")});}this[1].attr({x:f+i+j+o.width/2,y:m});k=(360-k)%360;this.rotate(k,f,m);k>90&&k<270&&this[1].attr({x:f-i-j-o.width/2,y:m,rotation:[180+k,f,m]});return this;};g.update();return g;};Raphael.fn.g.popupit=function(l,k,m,g,t){g=g==null?2:g;t=t||5;l=Math.round(l);k=Math.round(k);var j=m.getBBox(),n=Math.round(j.width/2),i=Math.round(j.height/2),s=[0,n+t*2,0,-n-t*2],o=[-i*2-t*3,-i-t,0,-i-t],f=["M",l-s[g],k-o[g],"l",-t,(g==2)*-t,-a(n-t,0),0,"a",t,t,0,0,1,-t,-t,"l",0,-a(i-t,0),(g==3)*-t,-t,(g==3)*t,-t,0,-a(i-t,0),"a",t,t,0,0,1,t,-t,"l",a(n-t,0),0,t,!g*-t,t,!g*t,a(n-t,0),0,"a",t,t,0,0,1,t,t,"l",0,a(i-t,0),(g==1)*t,t,(g==1)*-t,t,0,a(i-t,0),"a",t,t,0,0,1,-t,t,"l",-a(n-t,0),0,"z"].join(","),q=[{x:l,y:k+t*2+i},{x:l-t*2-n,y:k},{x:l,y:k-t*2-i},{x:l+t*2+n,y:k}][g];m.translate(q.x-n-j.x,q.y-i-j.y);return this.path(f).attr({fill:"#000",stroke:"none"}).insertBefore(m.node?m:m[0]);};Raphael.fn.g.popup=function(f,l,k,g,i){g=g==null?2:g>3?3:g;i=i||5;k=k||"$9.99";var h=this.set(),j=3;h.push(this.path().attr({fill:"#000",stroke:"#000"}));h.push(this.text(f,l,k).attr(this.g.txtattr).attr({fill:"#fff","font-family":"Helvetica, Arial"}));h.update=function(o,n,q){o=o||f;n=n||l;var t=this[1].getBBox(),u=t.width/2,s=t.height/2,y=[0,u+i*2,0,-u-i*2],v=[-s*2-i*3,-s-i,0,-s-i],m=["M",o-y[g],n-v[g],"l",-i,(g==2)*-i,-a(u-i,0),0,"a",i,i,0,0,1,-i,-i,"l",0,-a(s-i,0),(g==3)*-i,-i,(g==3)*i,-i,0,-a(s-i,0),"a",i,i,0,0,1,i,-i,"l",a(u-i,0),0,i,!g*-i,i,!g*i,a(u-i,0),0,"a",i,i,0,0,1,i,i,"l",0,a(s-i,0),(g==1)*i,i,(g==1)*-i,i,0,a(s-i,0),"a",i,i,0,0,1,-i,i,"l",-a(u-i,0),0,"z"].join(","),x=[{x:o,y:n+i*2+s},{x:o-i*2-u,y:n},{x:o,y:n-i*2-s},{x:o+i*2+u,y:n}][g];x.path=m;if(q){this.animate(x,500,">");}else{this.attr(x);}return this;};return h.update(f,l);};Raphael.fn.g.flag=function(f,k,j,i){i=i||0;j=j||"$9.99";var g=this.set(),h=3;g.push(this.path().attr({fill:"#000",stroke:"#000"}));g.push(this.text(f,k,j).attr(this.g.txtattr).attr({fill:"#fff","font-family":"Helvetica, Arial"}));g.update=function(l,o){this.rotate(0,l,o);var n=this[1].getBBox(),m=n.height/2;this[0].attr({path:["M",l,o,"l",m+h,-m-h,n.width+2*h,0,0,n.height+2*h,-n.width-2*h,0,"z"].join(",")});this[1].attr({x:l+m+h+n.width/2,y:o});i=360-i;this.rotate(i,l,o);i>90&&i<270&&this[1].attr({x:l-r-h-n.width/2,y:o,rotation:[180+i,l,o]});return this;};return g.update(f,k);};Raphael.fn.g.label=function(f,i,h){var g=this.set();g.push(this.rect(f,i,10,10).attr({stroke:"none",fill:"#000"}));g.push(this.text(f,i,h).attr(this.g.txtattr).attr({fill:"#fff"}));g.update=function(){var k=this[1].getBBox(),j=c(k.width+10,k.height+10)/2;this[0].attr({x:k.x-j/2,y:k.y-j/2,width:k.width+j,height:k.height+j,r:j});};g.update();return g;};Raphael.fn.g.labelit=function(h){var g=h.getBBox(),f=c(20,g.width+10,g.height+10)/2;return this.rect(g.x-f/2,g.y-f/2,g.width+f,g.height+f,f).attr({stroke:"none",fill:"#000"}).insertBefore(h.node?h:h[0]);};Raphael.fn.g.drop=function(f,k,j,h,i){h=h||30;i=i||0;var g=this.set();g.push(this.path(["M",f,k,"l",h,0,"A",h*0.4,h*0.4,0,1,0,f+h*0.7,k-h*0.7,"z"]).attr({fill:"#000",stroke:"none",rotation:[22.5-i,f,k]}));i=(i+90)*Math.PI/180;g.push(this.text(f+h*Math.sin(i),k+h*Math.cos(i),j).attr(this.g.txtattr).attr({"font-size":h*12/30,fill:"#fff"}));g.drop=g[0];g.text=g[1];return g;};Raphael.fn.g.blob=function(g,m,l,k,i){k=(+k+1?k:45)+90;i=i||12;var f=Math.PI/180,j=i*12/12;var h=this.set();h.push(this.path().attr({fill:"#000",stroke:"none"}));h.push(this.text(g+i*Math.sin((k)*f),m+i*Math.cos((k)*f)-j/2,l).attr(this.g.txtattr).attr({"font-size":j,fill:"#fff"}));h.update=function(t,s,y){t=t||g;s=s||m;var A=this[1].getBBox(),D=a(A.width+j,i*25/12),z=a(A.height+j,i*25/12),o=t+i*Math.sin((k-22.5)*f),B=s+i*Math.cos((k-22.5)*f),q=t+i*Math.sin((k+22.5)*f),C=s+i*Math.cos((k+22.5)*f),F=(q-o)/2,E=(C-B)/2,p=D/2,n=z/2,x=-Math.sqrt(Math.abs(p*p*n*n-p*p*E*E-n*n*F*F)/(p*p*E*E+n*n*F*F)),v=x*p*E/n+(q+o)/2,u=x*-n*F/p+(C+B)/2;if(y){this.animate({x:v,y:u,path:["M",g,m,"L",q,C,"A",p,n,0,1,1,o,B,"z"].join(",")},500,">");}else{this.attr({x:v,y:u,path:["M",g,m,"L",q,C,"A",p,n,0,1,1,o,B,"z"].join(",")});}return this;};h.update(g,m);return h;};Raphael.fn.g.colorValue=function(i,h,g,f){return"hsb("+[c((1-i/h)*0.4,1),g||0.75,f||0.75]+")";};Raphael.fn.g.snapEnds=function(n,o,m){var k=n,p=o;if(k==p){return{from:k,to:p,power:0};}function q(f){return Math.abs(f-0.5)<0.25?~~(f)+0.5:Math.round(f);}var l=(p-k)/m,g=~~(l),j=g,h=0;if(g){while(j){h--;j=~~(l*Math.pow(10,h))/Math.pow(10,h);}h++;}else{while(!g){h=h||1;g=~~(l*Math.pow(10,h))/Math.pow(10,h);h++;}h&&h--;}p=q(o*Math.pow(10,h))/Math.pow(10,h);if(p0?0:0.5))*Math.pow(10,h))/Math.pow(10,h);return{from:k,to:p,power:h};};Raphael.fn.g.axis=function(v,u,o,G,l,J,m,L,n,g){g=g==null?2:g;n=n||"t";J=J||10;var F=n=="|"||n==" "?["M",v+0.5,u,"l",0,0.001]:m==1||m==3?["M",v+0.5,u,"l",0,-o]:["M",v,u+0.5,"l",o,0],z=this.g.snapEnds(G,l,J),K=z.from,B=z.to,I=z.power,H=0,C=this.set();d=(B-K)/J;var s=K,q=I>0?I:0;w=o/J;if(+m==1||+m==3){var h=u,A=(m-1?1:-1)*(g+3+!!(m-1));while(h>=u-o){n!="-"&&n!=" "&&(F=F.concat(["M",v-(n=="+"||n=="|"?g:!(m-1)*g*2),h+0.5,"l",g*2+1,0]));C.push(this.text(v+A,h,(L&&L[H++])||(Math.round(s)==s?s:+s.toFixed(q))).attr(this.g.txtattr).attr({"text-anchor":m-1?"start":"end"}));s+=d;h-=w;}if(Math.round(h+w-(u-o))){n!="-"&&n!=" "&&(F=F.concat(["M",v-(n=="+"||n=="|"?g:!(m-1)*g*2),u-o+0.5,"l",g*2+1,0]));C.push(this.text(v+A,u-o,(L&&L[H])||(Math.round(s)==s?s:+s.toFixed(q))).attr(this.g.txtattr).attr({"text-anchor":m-1?"start":"end"}));}}else{s=K;q=(I>0)*I;A=(m?-1:1)*(g+9+!m);var k=v,w=o/J,D=0,E=0;while(k<=v+o){n!="-"&&n!=" "&&(F=F.concat(["M",k+0.5,u-(n=="+"?g:!!m*g*2),"l",0,g*2+1]));C.push(D=this.text(k,u+A,(L&&L[H++])||(Math.round(s)==s?s:+s.toFixed(q))).attr(this.g.txtattr));var p=D.getBBox();if(E>=p.x-5){C.pop(C.length-1).remove();}else{E=p.x+p.width;}s+=d;k+=w;}if(Math.round(k-w-v-o)){n!="-"&&n!=" "&&(F=F.concat(["M",v+o+0.5,u-(n=="+"?g:!!m*g*2),"l",0,g*2+1]));C.push(this.text(v+o,u+A,(L&&L[H])||(Math.round(s)==s?s:+s.toFixed(q))).attr(this.g.txtattr));}}var M=this.path(F);M.text=C;M.all=this.set([M,C]);M.remove=function(){this.text.remove();this.constructor.prototype.remove.call(this);};return M;};Raphael.el.lighter=function(g){g=g||2;var f=[this.attrs.fill,this.attrs.stroke];this.fs=this.fs||[f[0],f[1]];f[0]=Raphael.rgb2hsb(Raphael.getRGB(f[0]).hex);f[1]=Raphael.rgb2hsb(Raphael.getRGB(f[1]).hex);f[0].b=c(f[0].b*g,1);f[0].s=f[0].s/g;f[1].b=c(f[1].b*g,1);f[1].s=f[1].s/g;this.attr({fill:"hsb("+[f[0].h,f[0].s,f[0].b]+")",stroke:"hsb("+[f[1].h,f[1].s,f[1].b]+")"});};Raphael.el.darker=function(g){g=g||2;var f=[this.attrs.fill,this.attrs.stroke];this.fs=this.fs||[f[0],f[1]];f[0]=Raphael.rgb2hsb(Raphael.getRGB(f[0]).hex);f[1]=Raphael.rgb2hsb(Raphael.getRGB(f[1]).hex);f[0].s=c(f[0].s*g,1);f[0].b=f[0].b/g;f[1].s=c(f[1].s*g,1);f[1].b=f[1].b/g;this.attr({fill:"hsb("+[f[0].h,f[0].s,f[0].b]+")",stroke:"hsb("+[f[1].h,f[1].s,f[1].b]+")"});};Raphael.el.original=function(){if(this.fs){this.attr({fill:this.fs[0],stroke:this.fs[1]});delete this.fs;}};})(); -------------------------------------------------------------------------------- /lib/asset/htracr-comm.js: -------------------------------------------------------------------------------- 1 | if (! window.console) { 2 | window.console = { 3 | log: function log (msg) {} 4 | }; 5 | } 6 | 7 | if (! htracr) { 8 | var htracr = {}; 9 | } 10 | 11 | // server-side interactions 12 | htracr.comm = function () { 13 | var comm = { 14 | // start capturing a session 15 | start_capture: function () { 16 | var self = this; 17 | console.log('starting capture...'); 18 | var req = get_req(); 19 | req.onreadystatechange = function start_capture_response () { 20 | if (req.readyState === 4) { 21 | if (req.status == 200) { 22 | console.log('started.'); 23 | jQuery("#start").hide(); 24 | jQuery("#stop").show(); 25 | htracr.ui.pulse_logo(); 26 | } else { 27 | var error = eval("(" + req.responseText + ")"); 28 | alert("Sorry, I can't start the sniffer; it says \"" + 29 | error.message + "\"." 30 | ); 31 | console.log("start problem: " + error); 32 | } 33 | } 34 | }; 35 | req.open("POST", "/start", true); 36 | req.send(""); 37 | return false; 38 | }, 39 | 40 | // finish capturing a session 41 | stop_capture: function () { 42 | var self = this; 43 | console.log('stopping capture...'); 44 | var req = get_req(); 45 | req.onreadystatechange = function stop_capture_response () { 46 | if (req.readyState === 4) { 47 | self.update_state(); 48 | console.log('stopped.'); 49 | jQuery("#stop").hide(); 50 | jQuery("#start").show(); 51 | htracr.ui.unpulse_logo(true); 52 | } 53 | }; 54 | req.open("POST", "/stop", true); 55 | req.send(""); 56 | return false; 57 | }, 58 | 59 | // get the latest capture info from the server 60 | update_state: function () { 61 | var self = this; 62 | console.log('updating...'); 63 | var req = get_req(); 64 | req.onreadystatechange = function update_state_response () { 65 | if (req.readyState === 4) { 66 | if (req.status === 200) { 67 | var capture = JSON.parse(req.responseText); 68 | if (capture.error) { 69 | alert(capture.error.message); 70 | } 71 | htracr.ui.update(capture); 72 | console.log('updated.'); 73 | } else { 74 | console.log('no updates.'); 75 | } 76 | } 77 | }; 78 | req.open("GET", "/conns", true); 79 | req.send(""); 80 | return false; 81 | }, 82 | 83 | // remove capture info from the server 84 | clear_state: function () { 85 | var self = this; 86 | console.log('clearing...'); 87 | var req = get_req(); 88 | req.onreadystatechange = function clear_state_response () { 89 | if (req.readyState === 4) { 90 | htracr.ui.clear(); 91 | } 92 | }; 93 | req.open("POST", "/clear", true); 94 | req.send(""); 95 | return false; 96 | }, 97 | 98 | // get the list of server names (blocking) 99 | get_servers: function () { 100 | var self = this; 101 | console.log('getting servers...'); 102 | var req = get_req(); 103 | var server_names; 104 | req.onreadystatechange = function get_servers_response () { 105 | if (req.readyState === 4) { 106 | server_names = JSON.parse(req.responseText); 107 | } 108 | }; 109 | req.open("GET", "/servers", false); 110 | req.send(""); 111 | return server_names; 112 | }, 113 | 114 | // get a packet (blocking) 115 | get_packet: function (packet_id) { 116 | var req = get_req(); 117 | var data; 118 | req.onreadystatechange = function packet_fetch () { 119 | if (req.readyState === 4 && req.status === 200) { 120 | data = jQuery('
').text( 121 | JSON.parse(req.responseText).data 122 | ).html(); 123 | } 124 | }; 125 | // TODO: catch network errors. 126 | req.open("GET", "/packet/" + packet_id, false); 127 | req.send(""); 128 | return data; 129 | } 130 | }; 131 | 132 | 133 | // utility function for XHR 134 | function get_req () { 135 | var self = this; 136 | var req; 137 | if (window.XMLHttpRequest) { 138 | try { 139 | req = new XMLHttpRequest(); 140 | } catch(e1) { 141 | req = false; 142 | } 143 | } else if (window.ActiveXObject) { 144 | try { 145 | req = new ActiveXObject("Microsoft.XMLHTTP"); 146 | } catch(e2) { 147 | req = false; 148 | } 149 | } 150 | return req; 151 | } 152 | 153 | return comm; 154 | }(); 155 | -------------------------------------------------------------------------------- /lib/asset/htracr-data.js: -------------------------------------------------------------------------------- 1 | if (! window.console) { 2 | window.console = { 3 | log: function log (msg) {} 4 | }; 5 | } 6 | 7 | if (! htracr) { 8 | var htracr = {}; 9 | } 10 | 11 | htracr.res_colours = { 12 | 'text/html': "green", 13 | 'text/css': "purple", 14 | 'image/gif': "blue", 15 | 'image/jpeg': "blue", 16 | 'image/png': "blue", 17 | 'application/json': "yellow", 18 | 'application/xml': "yellow", 19 | 'application/x-javascript': "yellow" 20 | } 21 | 22 | // An item of interest that will be rendered. 23 | htracr.item = function (data) { 24 | 25 | // render the item 26 | data.draw = function (di, cursor) { 27 | var self = this; 28 | if (self.element) { 29 | delete self.element; 30 | } 31 | self.cursor = cursor; 32 | self.element = self._draw(di); 33 | self.element.orig_width = self.element.attr('stroke-width'); 34 | self.element.orig_opacity = self.element.attr('opacity'); 35 | self.element.click(function(event) { 36 | self.inspect(); 37 | htracr.ui.selected = self; 38 | }); 39 | }; 40 | 41 | data.inspect = function () { 42 | var self = this; 43 | var msg = ""; 44 | if (typeof self.element.html_msg === 'function') { 45 | msg = self.element.html_msg(); 46 | } else if (self.element.html_msg) { 47 | msg = self.element.html_msg; 48 | } 49 | if (htracr.ui.selected) { 50 | htracr.ui.selected.uninspect(); 51 | } 52 | htracr.ui.selected = self; 53 | htracr.ui.set_message(msg); 54 | self.element.attr({'opacity': "1"}); 55 | self.element.animate({ 56 | 'stroke-width': self.element.orig_width + 3 57 | }, 50, 58 | function () { 59 | self.element.animate({ 60 | 'stroke-width': self.element.orig_width 61 | }, 50); 62 | } 63 | ); 64 | }; 65 | 66 | data.uninspect = function () { 67 | var self = this; 68 | htracr.ui.set_message(""); 69 | self.element.attr({'opacity': self.element.orig_opacity}); 70 | htracr.ui.selected = undefined; 71 | }; 72 | 73 | // make sure start and end are set. 74 | if (! data.start) { 75 | data.start = htracr.ui.capture_idx.start; 76 | } 77 | if (! data.end) { 78 | data.end = htracr.ui.capture_idx.end; 79 | } 80 | 81 | data.getEndPoint = function (which) { 82 | var self = this; 83 | var offset; 84 | switch (which) { 85 | case -1: // start 86 | offset = 1; 87 | break; 88 | case 0: 89 | offset = (self.element.getTotalLength() - 1) / 2; 90 | break; 91 | case 1: // end 92 | offset = self.element.getTotalLength() - 1; 93 | break; 94 | default: 95 | console.log("unknown end " + which); 96 | offset = 1; 97 | break; 98 | } 99 | if (offset < 1) { 100 | offset = 1; 101 | } 102 | return this.element.getPointAtLength(offset); 103 | }; 104 | 105 | return data; 106 | }; 107 | 108 | 109 | htracr.connection = function(data) { 110 | var conn = htracr.item(data); 111 | 112 | conn._draw = function (di) { 113 | var self = this; 114 | var conn_e = di.h_line(self.start, self.end, 0, { 115 | "stroke": "#777", 116 | "stroke-width": htracr.ui.conn_size, 117 | "opacity": "0.6" 118 | }); 119 | conn_e.html_msg = "

Connection to " + di.server_name(conn.server) 120 | if (di.server_name != conn.server) { 121 | conn_e.html_msg += " (" + conn.server + ")"; 122 | } 123 | conn_e.html_msg += "

" + 124 | "
    " + 125 | "
  • Local port: " + conn.local_port + "
  • " + 126 | "
  • Duration: " + 127 | ((self.end - self.start) / 1000).toFixed(3) + 128 | " seconds
  • " + 129 | "
  • HTTP Requests: " + self.http_reqs.length + "
  • " + 130 | "
"; 131 | return conn_e; 132 | }; 133 | return conn; 134 | }; 135 | 136 | 137 | htracr.packet = function(data) { 138 | var packet = htracr.item(data); 139 | 140 | packet._draw = function (di) { 141 | var self = this; 142 | 143 | // what kind of packet am I? 144 | var len; 145 | var direction; 146 | switch (self.dir) { 147 | case "in": 148 | len = htracr.ui.conn_size / 2; 149 | direction = "<"; 150 | break; 151 | case "out": 152 | len = - (htracr.ui.conn_size / 2); 153 | direction = ">"; 154 | break; 155 | default: 156 | console.log("Unrecognised packet dir: " + self.dir); 157 | break; 158 | } 159 | 160 | // determine the packet colour 161 | var pcolour = '#bbb'; 162 | if (self.data_sz > 0) 163 | pcolour = '#ccc'; 164 | if (self.flags.psh) 165 | pcolour = 'green'; 166 | if (self.flags.syn) 167 | pcolour = 'yellow'; 168 | if (self.flags.rst) 169 | pcolour = 'blue'; 170 | if (self.flags.fin) 171 | pcolour = 'purple'; 172 | 173 | var pkt_e = di.v_line(self.time, len, { 174 | "stroke-width": htracr.ui.pix_per_sec <= 2500 ? "1" : "2", 175 | "stroke": pcolour, 176 | "shape-rendering": "crispEdges" 177 | }); 178 | 179 | if (self.ack_only) { 180 | htracr.ui.ack_elements.push(pkt_e); 181 | if (! htracr.ui.show_ack_elements) { 182 | pkt_e.hide(); 183 | } 184 | } 185 | 186 | pkt_e.html_msg = function () { 187 | var packet_data; 188 | if (self.data_sz) { 189 | packet_data = htracr.comm.get_packet(self.packet_id); 190 | } 191 | return "" + 192 | "" + 193 | "" + 194 | "" + 195 | "" + 196 | "" + 197 | "" + 198 | "" + 199 | "" + 200 | "
SYN ACK RST FIN PSH " + direction + "
" + 201 | "
    " + 202 | "
  • window size: " + self.ws + "
  • " + 203 | "
  • data bytes: " + self.data_sz + "
  • " + 204 | "
" + 205 | "
" + (packet_data || "") + "
"; 206 | }; 207 | 208 | return pkt_e; 209 | }; 210 | 211 | return packet; 212 | }; 213 | 214 | 215 | 216 | htracr.http_msg = function(data) { 217 | var msg = htracr.item(data); 218 | 219 | msg._draw = function (di) { 220 | var self = this; 221 | var a = { 222 | 'fill': 'white', 223 | 'opacity': '0.4', 224 | 'font-size': '11' 225 | }; 226 | 227 | var adj_y; 228 | var colour; 229 | switch (self.kind) { 230 | case "req": 231 | adj_y = - (htracr.ui.conn_size / 2) - (htracr.ui.msg_size / 2); 232 | di.save('referer', self.data.headers.Referer, self); 233 | var url = "http://" + self.data.headers.Host + self.data.url; 234 | di.save('request_uri', url, self, true); 235 | colour = "red"; 236 | break; 237 | case "res": 238 | adj_y = (htracr.ui.conn_size / 2) + (htracr.ui.msg_size / 2); 239 | di.save('location', self.data.headers.Location, self, true); 240 | var ct = (self.data.headers['Content-Type'] || "").split(";", 1)[0]; 241 | colour = htracr.res_colours[ct] || 'red'; 242 | break; 243 | default: 244 | console.log("Unknown message type: " + this.kind); 245 | break; 246 | } 247 | 248 | var msg_e = di.h_line(self.start, self.end, adj_y, { 249 | "stroke": colour, 250 | "stroke-linecap": "round", 251 | "stroke-width": "" + htracr.ui.msg_size, 252 | "opacity": ".6" 253 | }); 254 | 255 | var packet_count = 0; 256 | var byte_count = 0; 257 | var target_dir = self.kind === 'req' ? 'out' : 'in'; 258 | 259 | var desc = "

HTTP " + (self.kind === 'req' ? "Request" : "Response") + 260 | "

" + 261 | "
    " + 262 | "
  • Data packets: " + self.data_packet_count + "
  • " + 263 | "
  • Data size: " + self.packet_byte_count + " bytes
  • " + 264 | "
  • Duration: " + (self.end - self.start).toFixed(1) + "ms
  • " + 265 | "
";
266 | 
267 |     if (self.kind === 'req') {
268 |       desc += self.data.method + " " +
269 |               self.data.url +
270 |               " HTTP/" + self.data.http_version + "\n";
271 | //      if (msg.data.method !== 'GET') {
272 | //        self.paper.text(start_x + 20, msg_y - 10, msg.data.method).attr(a);
273 | //      }
274 |     } else {
275 |       desc += "HTTP/" + msg.data.http_version + " " +
276 |               msg.data.status_code + "\n";
277 | //      if (msg.data.status_code !== 200) {
278 | //        self.paper.text(
279 | //          start_x + 16, msg_y + 10, msg.data.status_code
280 | //        ).attr(a);
281 | //      }
282 |     }
283 |     for (var hdr in msg.data.headers) {
284 |       if (msg.data.headers.hasOwnProperty(hdr)) {
285 |         var val = msg.data.headers[hdr];
286 |         desc += hdr + ": " + val + "\n";          
287 |       }
288 |     }
289 |     desc += "
"; 290 | msg_e.html_msg = desc; 291 | return msg_e; 292 | }; 293 | 294 | return msg; 295 | }; 296 | -------------------------------------------------------------------------------- /lib/asset/htracr-ui.js: -------------------------------------------------------------------------------- 1 | if (! window.console) { 2 | window.console = { 3 | log: function log (msg) {} 4 | }; 5 | } 6 | 7 | Raphael.fn.curve = function (s, e) { 8 | if (! s.x || ! s.y || ! e.x || ! e.y) { 9 | // dummy 10 | console.log("Can't draw curve."); 11 | console.log(s); 12 | console.log(e); 13 | return undefined; 14 | } 15 | var s_ctrl = {x: s.x, y: s.y}; 16 | var e_ctrl = {x: s.x, y: s.y}; 17 | 18 | if (s.y == e.y) { 19 | s_ctrl.y = s.y - 30; 20 | e_ctrl.y = s.y - 30; 21 | } else { 22 | s_ctrl.y -= ((s.y - e.y) / 4); 23 | e_ctrl.y -= ((s.y - e.y) / 5); 24 | } 25 | var path = "M" + s.x + "," + s.y + " " + 26 | "C" + s_ctrl.x + "," + s_ctrl.y + " " + 27 | e_ctrl.x + "," + e_ctrl.y + " " + 28 | e.x + "," + e.y; 29 | return this.path(path); 30 | }; 31 | 32 | if (! htracr) { 33 | var htracr = {}; 34 | } 35 | 36 | // General Ui elements (non-data) 37 | htracr.ui = function () { 38 | var paper; 39 | var labels; 40 | var logo; 41 | var msg; 42 | var default_pix_per_sec = 500; // starting pixels per second 43 | var margin = [100, 20, 100, 20]; // top, right, bottom, left (pixels) 44 | 45 | 46 | // object for user interaction 47 | var ui = { 48 | 49 | // defaults and settings 50 | w: 550, // width (pixels) 51 | h: 400, // height (pixels) 52 | conn_size: 12, // how high connections are (pixels) 53 | msg_size: 8, // width of an http message (pixels) 54 | label_w: 600, // how wide labels are (pixels) 55 | conn_pad: 36, // padding between conns (pixels) 56 | server_pad: 48, // padding between servers (pixels) 57 | show_referer_elements: true, // default 58 | show_location_elements: true, // default 59 | show_ack_elements: true, // default 60 | 61 | // don't adjust these 62 | pix_per_sec: default_pix_per_sec, // pixels per second 63 | y: margin[0], // tracks y value 64 | urls: {}, // tracks url relationships 65 | server_names: undefined, // ip->name mapping 66 | capture: {}, // the capture 67 | capture_idx: {}, // index into the capture 68 | capturing: false, // whether we're capturing now 69 | selected: undefined, // selected item 70 | referer_elements: [], // referer elements 71 | location_elements: [], // location elements 72 | ack_elements: [], // ack-only packet elements 73 | 74 | // reset the UI and state to defaults 75 | clear: function () { 76 | var self = this; 77 | self.clear_state(); 78 | self.clear_ui(); 79 | }, 80 | 81 | clear_state: function () { 82 | var self = this; 83 | self.pix_per_sec = default_pix_per_sec; 84 | self.capture = {}; 85 | self.capture_idx = {}; 86 | self.server_names = undefined; 87 | self.show_referer_elements = true; 88 | self.show_location_elements = true; 89 | self.show_ack_elements = true; 90 | }, 91 | 92 | clear_ui: function() { 93 | var self = this; 94 | self.y = margin[0]; 95 | self.urls = {}; 96 | self.selected = undefined; 97 | self.referer_elements = []; 98 | self.location_elements = []; 99 | self.ack_elements = []; 100 | paper.clear(); 101 | labels.clear(); 102 | 103 | }, 104 | 105 | // update with new capture data and re-render 106 | update: function (capture) { 107 | var self = this; 108 | self.clear(); 109 | self.capture = capture; 110 | self.capture_idx = index_capture(capture); 111 | self.render(); 112 | }, 113 | 114 | // render the data 115 | render: function () { 116 | var self = this; 117 | 118 | self.clear_ui(); 119 | self.resize(); 120 | self.draw_scale(); 121 | 122 | self.capture_idx.servers.forEach(function (bundle) { 123 | var server_name = bundle[0]; 124 | var conn_ids = bundle[1]; 125 | var i; 126 | self.y += self.server_pad; 127 | self.draw_server_label(server_name); 128 | self.y += self.server_pad; 129 | conn_ids.forEach(function (conn_id) { 130 | var conn = self.capture.sessions[server_name][conn_id]; 131 | htracr.connection(conn).draw( 132 | di, [server_name, conn_id, undefined, 0]); 133 | i = 0; 134 | conn.http_reqs.forEach(function (http_req) { 135 | var msg = htracr.http_msg(http_req); 136 | msg.kind = "req"; 137 | msg.draw(di, [server_name, conn_id, 'http_reqs', i]); 138 | i += 1; 139 | }); 140 | i = 0; 141 | conn.http_ress.forEach(function (http_req) { 142 | var msg = htracr.http_msg(http_req); 143 | msg.kind = "res"; 144 | msg.draw(di, [server_name, conn_id, 'http_ress', i]); 145 | i += 1; 146 | }); 147 | i = 0; 148 | conn.packets.forEach(function (packet) { 149 | var pkt = htracr.packet(packet); 150 | pkt.draw(di, [server_name, conn_id, 'packets', i]); 151 | i += 1; 152 | }); 153 | self.y += self.conn_pad; 154 | }); 155 | 156 | self.draw_referers(); 157 | self.draw_locations(); 158 | }); 159 | }, 160 | 161 | // change the paper and label sizes to suit the capture 162 | resize: function() { 163 | var self = this; 164 | var idx = self.capture_idx; 165 | self.w = ( 166 | (idx.end - idx.start) / 1000 * self.pix_per_sec) + 167 | margin[1] + margin[3]; 168 | self.h = margin[0] + margin[2]; 169 | for (var s in idx.servers) { 170 | self.h += (self.server_pad * 2); 171 | self.h += ((idx.servers[s][1].length) * self.conn_pad); 172 | } 173 | console.log("resizing to " + self.w + " x " + self.h); 174 | paper.setSize(self.w, self.h); 175 | labels.setSize(self.label_w, self.h); 176 | }, 177 | 178 | // draw scale markings appropriate for the capture 179 | draw_scale: function () { 180 | var self = this; 181 | var start_x = time_x(self.capture_idx.start); 182 | var end_x = time_x(self.capture_idx.end); 183 | var end_y = self.h - margin[2]; 184 | var end_attrs = { 185 | stroke: "#666", 186 | "stroke-width": "1" 187 | }; 188 | var label_attrs = { 189 | fill: "white", 190 | "font-size": "16", 191 | "opacity": ".5" 192 | }; 193 | paper.path( 194 | "M" + start_x + "," + margin[0] + " " + 195 | "L" + start_x + "," + end_y).attr(end_attrs); 196 | var m; 197 | if (self.pix_per_sec <= 1000) { 198 | m = 1000; 199 | } else if (self.pix_per_sec <= 2000) { 200 | m = 500; 201 | } else if (self.pix_per_sec <= 3500) { 202 | m = 250; 203 | } else if (self.pix_per_sec <= 5000) { 204 | m = 100; 205 | } else { 206 | m = 50; 207 | } 208 | for (var i = self.capture_idx.start; i < self.capture_idx.end; i += m) { 209 | var i_x = time_x(i); 210 | paper.path( 211 | "M" + i_x + "," + margin[0] + " " + 212 | "L" + i_x + "," + end_y 213 | ).attr({ 214 | stroke: "#444", 215 | "stroke-width": "1" 216 | }); 217 | paper.text(i_x, end_y + 10, 218 | ((i - self.capture_idx.start) / 1000) + "s").attr(label_attrs); 219 | paper.text(i_x, margin[0] - 20, 220 | ((i - self.capture_idx.start) / 1000) + "s").attr(label_attrs); 221 | } 222 | paper.path( 223 | "M" + end_x + "," + margin[0] + " " + 224 | "L" + end_x + "," + end_y 225 | ).attr(end_attrs); 226 | }, 227 | 228 | draw_server_label: function (label) { 229 | var self = this; 230 | var server_name = di.server_name(label); 231 | labels.text(margin[3] * 2, self.y, server_name).attr({ 232 | 'font-size': 20, 233 | 'text-anchor': 'start', 234 | 'font-weight': 'bold', 235 | 'fill': "#ccc", 236 | 'opacity': '0.7' 237 | }); 238 | }, 239 | 240 | draw_referers: function () { 241 | var self = this; 242 | for (var referer in self.urls.referer) { 243 | if (self.urls.request_uri[referer]) { 244 | 245 | var s = self.urls.request_uri[referer].getEndPoint(1); 246 | self.urls.referer[referer].forEach(function (referee) { 247 | var e = referee.getEndPoint(-1); 248 | var ref_e = paper.curve(s, e); 249 | if (ref_e) { 250 | ref_e.attr({ 251 | "stroke": "#aac", 252 | "stroke-width": "0.25px", 253 | "stroke-opacity": "0.25", 254 | "shape-rendering": "optimizeSpeed", 255 | "color-rendering": "optimizeSpeed" 256 | }); 257 | ref_e.toBack(); 258 | if (! self.show_referer_elements) { 259 | ref_e.hide(); 260 | } 261 | self.referer_elements.push(ref_e); 262 | } 263 | }); 264 | } 265 | } 266 | }, 267 | 268 | draw_locations: function () { 269 | var self = this; 270 | for (var location in self.urls.location) { 271 | var request_node = self.urls.request_uri[location]; 272 | if (request_node) { 273 | var e = request_node.getEndPoint(1); 274 | var s = self.urls.location[location].getEndPoint(-1); 275 | var loc_e = paper.curve(s, e); 276 | if (loc_e) { 277 | loc_e.attr({ 278 | "stroke": "#9c9", 279 | "stroke-width": "2px", 280 | "stroke-opacity": "0.6" 281 | }); 282 | loc_e.toBack(); 283 | if (! self.show_location_elements) { 284 | loc_e.hide(); 285 | } 286 | self.location_elements.push(loc_e); 287 | } 288 | } 289 | } 290 | }, 291 | 292 | // change the zoom level and re-render 293 | zoom: function (val) { 294 | var self = this; 295 | console.log("zooming to " + val + "..."); 296 | self.pix_per_sec = val; 297 | self.render(); 298 | }, 299 | 300 | // set the message area's content 301 | set_message: function (content) { 302 | msg.html(content); 303 | }, 304 | 305 | toggle_array: function (eid, arr_name) { 306 | var self = this; 307 | jQuery(eid).toggle(function (ev) { 308 | self[arr_name].forEach(function (e) { 309 | e.hide(); 310 | }); 311 | jQuery(eid).removeClass("on").addClass("off"); 312 | self["show_" + arr_name] = false; 313 | }, function (ev) { 314 | self[arr_name].forEach(function (e) { 315 | e.show(); 316 | }); 317 | jQuery(eid).removeClass("off").addClass("on"); 318 | self["show_" + arr_name] = true; 319 | }); 320 | }, 321 | 322 | draw_logo: function () { 323 | if (logo === undefined) { 324 | logo = paper.text(0, 50, "htracr").attr({ 325 | "fill": "white", 326 | "font-size": "120", 327 | 'text-anchor': 'start', 328 | 'opacity': "0.02" 329 | }); 330 | } 331 | }, 332 | 333 | pulse_logo: function() { 334 | var self = this; 335 | self.capturing = true; 336 | if (logo === undefined) { 337 | self.draw_logo(); 338 | } 339 | logo.animate({ 340 | 'opacity': '0.6', 341 | 'fill': '#669' 342 | }, 343 | 1500, function() { 344 | self.unpulse_logo(); 345 | }); 346 | }, 347 | 348 | unpulse_logo: function(done) { 349 | var self = this; 350 | if (done) { 351 | self.capturing = false; 352 | } 353 | if (logo === undefined) { 354 | draw(); 355 | } 356 | logo.animate({ 357 | 'opacity': '0.02', 358 | 'fill': 'white' 359 | }, 360 | 1500, function() { 361 | if (self.capturing) { 362 | self.pulse_logo(); 363 | } 364 | }); 365 | }, 366 | 367 | handle_key: function (ch) { 368 | var self = this; 369 | if (! self.selected) { 370 | return true; 371 | } 372 | var cursor = self.selected.cursor.slice(); 373 | var conn = self.capture.sessions[cursor[0]][cursor[1]]; 374 | switch (ch.keyCode) { 375 | case 37: // left 376 | cursor[3] = Math.max(0, cursor[3] - 1); 377 | break; 378 | case 39: // right 379 | cursor[3] = Math.min(conn[cursor[2]].length - 1, cursor[3] + 1); 380 | break; 381 | case 38: // up 382 | switch (cursor[2]) { 383 | case 'packets': 384 | if (self.selected.msg != undefined) { 385 | cursor[2] = 'http_reqs'; 386 | cursor[3] = self.selected.msg; 387 | } 388 | break; 389 | case 'http_reqs': 390 | return true; 391 | case 'http_ress': 392 | cursor[2] = 'packets'; 393 | cursor[3] = self.selected.start_packet; 394 | break; 395 | default: 396 | console.log('Unknown cursor kind: ' + cursor[2]); 397 | return true; 398 | } 399 | break; 400 | case 40: // down 401 | switch (cursor[2]) { 402 | case 'packets': 403 | if (self.selected.msg != undefined) { 404 | console.log('found down for packet!'); 405 | cursor[2] = 'http_ress'; 406 | cursor[3] = self.selected.msg; 407 | } 408 | break; 409 | case 'http_reqs': 410 | cursor[2] = 'packets'; 411 | cursor[3] = self.selected.start_packet; 412 | break; 413 | case 'http_ress': 414 | return true; 415 | default: 416 | console.log('Unknown cursor kind: ' + cursor[2]); 417 | return true; 418 | } 419 | break; 420 | default: 421 | return true; 422 | } 423 | var server = cursor[0]; 424 | var conn_id = cursor[1]; 425 | var kind = cursor[2]; 426 | var offset = cursor[3]; 427 | if (kind === undefined) { 428 | return false; 429 | } 430 | self.selected.uninspect(); 431 | self.selected = self.capture.sessions[cursor[0]] 432 | [cursor[1]] 433 | [cursor[2]] 434 | [cursor[3]]; 435 | self.selected.inspect(); 436 | return false; 437 | } 438 | 439 | }; 440 | 441 | paper = new Raphael(document.getElementById("paper"), ui.w, ui.h); 442 | labels = new Raphael(document.getElementById("labels"), ui.label_w, ui.h); 443 | msg = jQuery("#msg"); 444 | 445 | 446 | // object for data drawing use 447 | var di = { 448 | // draw a horizontal line from the time offsets start to end with 449 | // attrs. If start or end isn't set, use the left and right margins 450 | // respectively. y_adj can be used to adjust y. 451 | h_line: function (start, end, y_adj, attrs) { 452 | var start_x = time_x(start) || margin[3]; 453 | var end_x = time_x(end) || ui.w - margin[1]; 454 | var y = ui.y + y_adj; 455 | var e = paper.path( 456 | "M" + start_x + "," + y + " " + 457 | "L" + end_x + "," + y 458 | ).attr(attrs || {}); 459 | return e; 460 | }, 461 | 462 | // draw a line at when from y as far as len pixels with attrs. 463 | v_line: function (when, len, attrs) { 464 | var x = time_x(when); 465 | var end_y = ui.y + len; 466 | var e = paper.path( 467 | "M" + x + "," + ui.y + " " + 468 | "L" + x + "," + end_y 469 | ).attr(attrs || {}); 470 | return e; 471 | }, 472 | 473 | server_name: function (server) { 474 | var self = this; 475 | if (ui.server_names === undefined) { 476 | ui.server_names = htracr.comm.get_servers(); 477 | } 478 | return ui.server_names[server] || server; 479 | }, 480 | 481 | // save the cursor location for a named value 482 | save: function (name, value, cursor, single) { 483 | if (value) { 484 | if (! htracr.ui.urls[name]) { 485 | ui.urls[name] = {}; 486 | } 487 | if (single === true) { 488 | ui.urls[name][value] = cursor; 489 | } else { 490 | if (! ui.urls[name][value]) { 491 | ui.urls[name][value] = []; 492 | } 493 | ui.urls[name][value].push(cursor); 494 | } 495 | } 496 | } 497 | }; 498 | 499 | // given a time, return the corresponding x value for paper 500 | function time_x (t) { 501 | var self = this; 502 | if (t === null) { 503 | return null; 504 | } 505 | var delta = t - ui.capture_idx.start; 506 | if (delta < 0) { 507 | console.log('Negative delta for time ' + t); 508 | } 509 | var pix = delta * ui.pix_per_sec / 1000; 510 | var x = margin[3] + pix; 511 | return x; 512 | } 513 | 514 | // Given a hash of {server: {conn_id: [...]}}, return an ordered list 515 | // of [server_name, [conn_id1...], start], based upon first connection. 516 | function index_capture (capture) { 517 | 518 | function server_sortfunc (a, b) { 519 | return capture.sessions[a[0]][a[1][a[1].length - 1]].start - 520 | capture.sessions[b[0]][b[1][b[1].length - 1]] 521 | } 522 | 523 | function conn_sortfunc (a, b) { 524 | return a.start - b.start; 525 | } 526 | 527 | var servers = []; 528 | var start; 529 | var end; 530 | for (var server_id in capture.sessions) { 531 | if (capture.sessions.hasOwnProperty(server_id)) { 532 | var conns = []; 533 | for (var conn_id in capture.sessions[server_id]) { 534 | if (capture.sessions[server_id].hasOwnProperty(conn_id)) { 535 | var conn = capture.sessions[server_id][conn_id]; 536 | if (conn.http_reqs.length) { 537 | conns.push(conn); 538 | } 539 | } 540 | } 541 | conns.sort(conn_sortfunc); 542 | var conn_ids = []; 543 | conns.forEach(function (conn) { 544 | conn_ids.push(conn.local_port); 545 | 546 | }) 547 | 548 | if (conn_ids.length) { 549 | if (! start || conns[0].start < start) { 550 | start = conns[0].start || capture.start; 551 | } 552 | if (! end || conns[conns.length - 1].end > end) { 553 | end = conns[conns.length - 1].end || capture.end; 554 | } 555 | servers.push([server_id, conn_ids]); 556 | } 557 | } 558 | } 559 | servers.sort(server_sortfunc); 560 | return { 561 | start: start, 562 | end: end, 563 | servers: servers 564 | }; 565 | } 566 | 567 | // setup 568 | jQuery.noConflict(); 569 | jQuery(document).ready(function () { 570 | jQuery("#stop").hide(); 571 | jQuery("#filename").hide(); 572 | jQuery("#panel").draggable(); 573 | jQuery("#panel").resizable({ 574 | stop: function (event, ui) { 575 | jQuery("#panel").css('position', 'fixed'); 576 | } 577 | }); 578 | jQuery("#zoom").slider({ 579 | max: 10000, 580 | min: 100, 581 | step: 50, 582 | value: default_pix_per_sec, 583 | change: function (event, slider) { 584 | var orig_w = ui.w; 585 | var orig_scroll = jQuery(window).scrollLeft(); 586 | ui.zoom(slider.value); 587 | var new_w = ui.w; 588 | var new_scroll = (orig_scroll / orig_w) * new_w; 589 | jQuery(window).scrollLeft(new_scroll); 590 | } 591 | }); 592 | jQuery("#show-upload").click(function() { 593 | jQuery("#show-upload").hide(); 594 | jQuery("#filename").show(); 595 | jQuery("#filename").change(function() { 596 | jQuery("#upload").submit(); 597 | }); 598 | }); 599 | ui.toggle_array("#refs", "referer_elements"); 600 | ui.toggle_array("#redirs", "location_elements"); 601 | ui.toggle_array("#acks", "ack_elements"); 602 | jQuery(window).scroll(function () { 603 | jQuery('#labels').css('top', -(jQuery(window).scrollTop())); 604 | }); 605 | jQuery("html").keydown(function(ch){return ui.handle_key(ch)}); 606 | htracr.comm.update_state(); 607 | htracr.ui.draw_logo(); 608 | }); 609 | 610 | return ui; 611 | }(); 612 | -------------------------------------------------------------------------------- /lib/asset/htracr.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, Helvetica, sans-serif;; 3 | background-color: #1c1a1c; 4 | color: white; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | #panel { 9 | position: fixed; 10 | bottom: 20px; 11 | right: 30px; 12 | width: 550px; 13 | height: 250px; 14 | background-color: black; 15 | opacity: 0.75; 16 | border-radius: 1em; 17 | padding: 1.5em; 18 | overflow: hidden; 19 | } 20 | #msg { 21 | position: absolute; 22 | } 23 | table { 24 | font-family: Arial, Helvetica, sans-serif;; 25 | font-size: 12px; 26 | } 27 | td.direction { 28 | font-weight: bolder; 29 | font-size: 18px; 30 | width: 20px; 31 | text-align: right; 32 | } 33 | pre { 34 | font-family: monospace; 35 | font-size: 0.85em; 36 | } 37 | #paper { 38 | margin: 0; 39 | z-index: 5; 40 | } 41 | #labels { 42 | position: fixed; 43 | left: 10px; 44 | z-index: -1; 45 | } 46 | #control { 47 | position: fixed; 48 | bottom: 20px; 49 | left: 20px; 50 | z-index: 10; 51 | } 52 | #features { 53 | margin-top: 6px; 54 | font-size: 12px; 55 | } 56 | #features * { 57 | padding: 2px; 58 | margin-right: 3px; 59 | } 60 | #zoom { 61 | width: 200px; 62 | font-size: 10px; 63 | display: inline-block; 64 | } 65 | .on { 66 | background-color: #ddeedd; 67 | color: black; 68 | } 69 | .off { 70 | background-color: #454545; 71 | color: black; 72 | } 73 | .close-btn { 74 | display: block; 75 | position: absolute; 76 | top: -8px; 77 | right: -8px; 78 | height: 0; 79 | width: 18px; 80 | padding: 18px 0 0 0; 81 | overflow: hidden; 82 | background: #000000 none; 83 | border: 2.04545454545455px solid #ffffff; 84 | -moz-border-radius: 18px; 85 | -webkit-border-radius: 18px; 86 | border-radius: 18px; 87 | box-shadow: 0 0 6px #000000, 1.63636363636364px 1.63636363636364px 1.63636363636364px rgba(0, 0, 0, 0.3), -1.63636363636364px 1.63636363636364px 1.63636363636364px rgba(0, 0, 0, 0.3), 1.63636363636364px -1.63636363636364px 1.63636363636364px rgba(0, 0, 0, 0.3), -1.63636363636364px -1.63636363636364px 1.63636363636364px rgba(0, 0, 0, 0.3); 88 | -ms-box-shadow: 0 0 6px #000000, 1.63636363636364px 1.63636363636364px 1.63636363636364px rgba(0, 0, 0, 0.3), -1.63636363636364px 1.63636363636364px 1.63636363636364px rgba(0, 0, 0, 0.3), 1.63636363636364px -1.63636363636364px 1.63636363636364px rgba(0, 0, 0, 0.3), -1.63636363636364px -1.63636363636364px 1.63636363636364px rgba(0, 0, 0, 0.3); 89 | -moz-box-shadow: 0 0 6px #000000, 1.63636363636364px 1.63636363636364px 1.63636363636364px rgba(0, 0, 0, 0.3), -1.63636363636364px 1.63636363636364px 1.63636363636364px rgba(0, 0, 0, 0.3), 1.63636363636364px -1.63636363636364px 1.63636363636364px rgba(0, 0, 0, 0.3), -1.63636363636364px -1.63636363636364px 1.63636363636364px rgba(0, 0, 0, 0.3); 90 | -webkit-box-shadow: 0 0 6px #000000, 1.63636363636364px 1.63636363636364px 1.63636363636364px rgba(0, 0, 0, 0.3), -1.63636363636364px 1.63636363636364px 1.63636363636364px rgba(0, 0, 0, 0.3), 1.63636363636364px -1.63636363636364px 1.63636363636364px rgba(0, 0, 0, 0.3), -1.63636363636364px -1.63636363636364px 1.63636363636364px rgba(0, 0, 0, 0.3); 91 | color: #ffffff; 92 | cursor: pointer; 93 | -moz-user-select: none; 94 | -webkit-user-select: none; 95 | user-select: none; 96 | } 97 | .close-btn:before { 98 | content: "\D7"; 99 | display: block; 100 | text-align: center; 101 | width: 18px; 102 | position: absolute; 103 | top: -1.8px; 104 | left: 0; 105 | font-size: 18px; 106 | line-height: 18px; 107 | font-family: "Helvetica Neue", Consolas, Verdana, Tahoma, Calibri, Helvetica, Menlo, "Droid Sans", sans-serif; 108 | top: -2px; 109 | left: 1px; 110 | } 111 | -------------------------------------------------------------------------------- /lib/asset/htracr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | htracr 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 |
26 | 27 | 28 | 29 | 30 |
31 |
32 |
33 |
34 | referers 35 | redirects 36 | acks 37 |
38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /lib/asset/jquery-ui.css: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery UI CSS Framework 1.8.6 3 | * 4 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Theming/API 9 | */ 10 | 11 | /* Layout helpers 12 | ----------------------------------*/ 13 | .ui-helper-hidden { display: none; } 14 | .ui-helper-hidden-accessible { position: absolute; left: -99999999px; } 15 | .ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } 16 | .ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } 17 | .ui-helper-clearfix { display: inline-block; } 18 | /* required comment for clearfix to work in Opera \*/ 19 | * html .ui-helper-clearfix { height:1%; } 20 | .ui-helper-clearfix { display:block; } 21 | /* end clearfix */ 22 | .ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } 23 | 24 | 25 | /* Interaction Cues 26 | ----------------------------------*/ 27 | .ui-state-disabled { cursor: default !important; } 28 | 29 | 30 | /* Icons 31 | ----------------------------------*/ 32 | 33 | /* states and images */ 34 | .ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } 35 | 36 | 37 | /* Misc visuals 38 | ----------------------------------*/ 39 | 40 | /* Overlays */ 41 | .ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } 42 | 43 | 44 | /* 45 | * jQuery UI CSS Framework 1.8.6 46 | * 47 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 48 | * Dual licensed under the MIT or GPL Version 2 licenses. 49 | * http://jquery.org/license 50 | * 51 | * http://docs.jquery.com/UI/Theming/API 52 | * 53 | * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Segoe%20UI,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=6px&bgColorHeader=333333&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=25&borderColorHeader=333333&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=000000&bgTextureContent=05_inset_soft.png&bgImgOpacityContent=25&borderColorContent=666666&fcContent=ffffff&iconColorContent=cccccc&bgColorDefault=555555&bgTextureDefault=02_glass.png&bgImgOpacityDefault=20&borderColorDefault=666666&fcDefault=eeeeee&iconColorDefault=cccccc&bgColorHover=0078a3&bgTextureHover=02_glass.png&bgImgOpacityHover=40&borderColorHover=59b4d4&fcHover=ffffff&iconColorHover=ffffff&bgColorActive=f58400&bgTextureActive=05_inset_soft.png&bgImgOpacityActive=30&borderColorActive=ffaf0f&fcActive=ffffff&iconColorActive=222222&bgColorHighlight=eeeeee&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=80&borderColorHighlight=cccccc&fcHighlight=2e7db2&iconColorHighlight=4b8e0b&bgColorError=ffc73d&bgTextureError=02_glass.png&bgImgOpacityError=40&borderColorError=ffb73d&fcError=111111&iconColorError=a83300&bgColorOverlay=5c5c5c&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=50&opacityOverlay=80&bgColorShadow=cccccc&bgTextureShadow=01_flat.png&bgImgOpacityShadow=30&opacityShadow=60&thicknessShadow=7px&offsetTopShadow=-7px&offsetLeftShadow=-7px&cornerRadiusShadow=8px 54 | */ 55 | 56 | 57 | /* Component containers 58 | ----------------------------------*/ 59 | .ui-widget { font-family: Segoe UI, Arial, sans-serif; font-size: 1.1em; } 60 | .ui-widget .ui-widget { font-size: 1em; } 61 | .ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Segoe UI, Arial, sans-serif; font-size: 1em; } 62 | .ui-widget-content { border: 1px solid #666666; background: #000000 url(images/ui-bg_inset-soft_25_000000_1x100.png) 50% bottom repeat-x; color: #ffffff; } 63 | .ui-widget-content a { color: #ffffff; } 64 | .ui-widget-header { border: 1px solid #333333; background: #333333 url(images/ui-bg_gloss-wave_25_333333_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; } 65 | .ui-widget-header a { color: #ffffff; } 66 | 67 | /* Interaction states 68 | ----------------------------------*/ 69 | .ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #666666; background: #555555 url(images/ui-bg_glass_20_555555_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #eeeeee; } 70 | .ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #eeeeee; text-decoration: none; } 71 | .ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #59b4d4; background: #0078a3 url(images/ui-bg_glass_40_0078a3_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #ffffff; } 72 | .ui-state-hover a, .ui-state-hover a:hover { color: #ffffff; text-decoration: none; } 73 | .ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #ffaf0f; background: #f58400 url(images/ui-bg_inset-soft_30_f58400_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #ffffff; } 74 | .ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #ffffff; text-decoration: none; } 75 | .ui-widget :active { outline: none; } 76 | 77 | /* Interaction Cues 78 | ----------------------------------*/ 79 | .ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #cccccc; background: #eeeeee url(images/ui-bg_highlight-soft_80_eeeeee_1x100.png) 50% top repeat-x; color: #2e7db2; } 80 | .ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #2e7db2; } 81 | .ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #ffb73d; background: #ffc73d url(images/ui-bg_glass_40_ffc73d_1x400.png) 50% 50% repeat-x; color: #111111; } 82 | .ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #111111; } 83 | .ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #111111; } 84 | .ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } 85 | .ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } 86 | .ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } 87 | 88 | /* Icons 89 | ----------------------------------*/ 90 | 91 | /* states and images */ 92 | .ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_cccccc_256x240.png); } 93 | .ui-widget-content .ui-icon {background-image: url(images/ui-icons_cccccc_256x240.png); } 94 | .ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } 95 | .ui-state-default .ui-icon { background-image: url(images/ui-icons_cccccc_256x240.png); } 96 | .ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); } 97 | .ui-state-active .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); } 98 | .ui-state-highlight .ui-icon {background-image: url(images/ui-icons_4b8e0b_256x240.png); } 99 | .ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_a83300_256x240.png); } 100 | 101 | /* positioning */ 102 | .ui-icon-carat-1-n { background-position: 0 0; } 103 | .ui-icon-carat-1-ne { background-position: -16px 0; } 104 | .ui-icon-carat-1-e { background-position: -32px 0; } 105 | .ui-icon-carat-1-se { background-position: -48px 0; } 106 | .ui-icon-carat-1-s { background-position: -64px 0; } 107 | .ui-icon-carat-1-sw { background-position: -80px 0; } 108 | .ui-icon-carat-1-w { background-position: -96px 0; } 109 | .ui-icon-carat-1-nw { background-position: -112px 0; } 110 | .ui-icon-carat-2-n-s { background-position: -128px 0; } 111 | .ui-icon-carat-2-e-w { background-position: -144px 0; } 112 | .ui-icon-triangle-1-n { background-position: 0 -16px; } 113 | .ui-icon-triangle-1-ne { background-position: -16px -16px; } 114 | .ui-icon-triangle-1-e { background-position: -32px -16px; } 115 | .ui-icon-triangle-1-se { background-position: -48px -16px; } 116 | .ui-icon-triangle-1-s { background-position: -64px -16px; } 117 | .ui-icon-triangle-1-sw { background-position: -80px -16px; } 118 | .ui-icon-triangle-1-w { background-position: -96px -16px; } 119 | .ui-icon-triangle-1-nw { background-position: -112px -16px; } 120 | .ui-icon-triangle-2-n-s { background-position: -128px -16px; } 121 | .ui-icon-triangle-2-e-w { background-position: -144px -16px; } 122 | .ui-icon-arrow-1-n { background-position: 0 -32px; } 123 | .ui-icon-arrow-1-ne { background-position: -16px -32px; } 124 | .ui-icon-arrow-1-e { background-position: -32px -32px; } 125 | .ui-icon-arrow-1-se { background-position: -48px -32px; } 126 | .ui-icon-arrow-1-s { background-position: -64px -32px; } 127 | .ui-icon-arrow-1-sw { background-position: -80px -32px; } 128 | .ui-icon-arrow-1-w { background-position: -96px -32px; } 129 | .ui-icon-arrow-1-nw { background-position: -112px -32px; } 130 | .ui-icon-arrow-2-n-s { background-position: -128px -32px; } 131 | .ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } 132 | .ui-icon-arrow-2-e-w { background-position: -160px -32px; } 133 | .ui-icon-arrow-2-se-nw { background-position: -176px -32px; } 134 | .ui-icon-arrowstop-1-n { background-position: -192px -32px; } 135 | .ui-icon-arrowstop-1-e { background-position: -208px -32px; } 136 | .ui-icon-arrowstop-1-s { background-position: -224px -32px; } 137 | .ui-icon-arrowstop-1-w { background-position: -240px -32px; } 138 | .ui-icon-arrowthick-1-n { background-position: 0 -48px; } 139 | .ui-icon-arrowthick-1-ne { background-position: -16px -48px; } 140 | .ui-icon-arrowthick-1-e { background-position: -32px -48px; } 141 | .ui-icon-arrowthick-1-se { background-position: -48px -48px; } 142 | .ui-icon-arrowthick-1-s { background-position: -64px -48px; } 143 | .ui-icon-arrowthick-1-sw { background-position: -80px -48px; } 144 | .ui-icon-arrowthick-1-w { background-position: -96px -48px; } 145 | .ui-icon-arrowthick-1-nw { background-position: -112px -48px; } 146 | .ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } 147 | .ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } 148 | .ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } 149 | .ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } 150 | .ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } 151 | .ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } 152 | .ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } 153 | .ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } 154 | .ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } 155 | .ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } 156 | .ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } 157 | .ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } 158 | .ui-icon-arrowreturn-1-w { background-position: -64px -64px; } 159 | .ui-icon-arrowreturn-1-n { background-position: -80px -64px; } 160 | .ui-icon-arrowreturn-1-e { background-position: -96px -64px; } 161 | .ui-icon-arrowreturn-1-s { background-position: -112px -64px; } 162 | .ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } 163 | .ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } 164 | .ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } 165 | .ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } 166 | .ui-icon-arrow-4 { background-position: 0 -80px; } 167 | .ui-icon-arrow-4-diag { background-position: -16px -80px; } 168 | .ui-icon-extlink { background-position: -32px -80px; } 169 | .ui-icon-newwin { background-position: -48px -80px; } 170 | .ui-icon-refresh { background-position: -64px -80px; } 171 | .ui-icon-shuffle { background-position: -80px -80px; } 172 | .ui-icon-transfer-e-w { background-position: -96px -80px; } 173 | .ui-icon-transferthick-e-w { background-position: -112px -80px; } 174 | .ui-icon-folder-collapsed { background-position: 0 -96px; } 175 | .ui-icon-folder-open { background-position: -16px -96px; } 176 | .ui-icon-document { background-position: -32px -96px; } 177 | .ui-icon-document-b { background-position: -48px -96px; } 178 | .ui-icon-note { background-position: -64px -96px; } 179 | .ui-icon-mail-closed { background-position: -80px -96px; } 180 | .ui-icon-mail-open { background-position: -96px -96px; } 181 | .ui-icon-suitcase { background-position: -112px -96px; } 182 | .ui-icon-comment { background-position: -128px -96px; } 183 | .ui-icon-person { background-position: -144px -96px; } 184 | .ui-icon-print { background-position: -160px -96px; } 185 | .ui-icon-trash { background-position: -176px -96px; } 186 | .ui-icon-locked { background-position: -192px -96px; } 187 | .ui-icon-unlocked { background-position: -208px -96px; } 188 | .ui-icon-bookmark { background-position: -224px -96px; } 189 | .ui-icon-tag { background-position: -240px -96px; } 190 | .ui-icon-home { background-position: 0 -112px; } 191 | .ui-icon-flag { background-position: -16px -112px; } 192 | .ui-icon-calendar { background-position: -32px -112px; } 193 | .ui-icon-cart { background-position: -48px -112px; } 194 | .ui-icon-pencil { background-position: -64px -112px; } 195 | .ui-icon-clock { background-position: -80px -112px; } 196 | .ui-icon-disk { background-position: -96px -112px; } 197 | .ui-icon-calculator { background-position: -112px -112px; } 198 | .ui-icon-zoomin { background-position: -128px -112px; } 199 | .ui-icon-zoomout { background-position: -144px -112px; } 200 | .ui-icon-search { background-position: -160px -112px; } 201 | .ui-icon-wrench { background-position: -176px -112px; } 202 | .ui-icon-gear { background-position: -192px -112px; } 203 | .ui-icon-heart { background-position: -208px -112px; } 204 | .ui-icon-star { background-position: -224px -112px; } 205 | .ui-icon-link { background-position: -240px -112px; } 206 | .ui-icon-cancel { background-position: 0 -128px; } 207 | .ui-icon-plus { background-position: -16px -128px; } 208 | .ui-icon-plusthick { background-position: -32px -128px; } 209 | .ui-icon-minus { background-position: -48px -128px; } 210 | .ui-icon-minusthick { background-position: -64px -128px; } 211 | .ui-icon-close { background-position: -80px -128px; } 212 | .ui-icon-closethick { background-position: -96px -128px; } 213 | .ui-icon-key { background-position: -112px -128px; } 214 | .ui-icon-lightbulb { background-position: -128px -128px; } 215 | .ui-icon-scissors { background-position: -144px -128px; } 216 | .ui-icon-clipboard { background-position: -160px -128px; } 217 | .ui-icon-copy { background-position: -176px -128px; } 218 | .ui-icon-contact { background-position: -192px -128px; } 219 | .ui-icon-image { background-position: -208px -128px; } 220 | .ui-icon-video { background-position: -224px -128px; } 221 | .ui-icon-script { background-position: -240px -128px; } 222 | .ui-icon-alert { background-position: 0 -144px; } 223 | .ui-icon-info { background-position: -16px -144px; } 224 | .ui-icon-notice { background-position: -32px -144px; } 225 | .ui-icon-help { background-position: -48px -144px; } 226 | .ui-icon-check { background-position: -64px -144px; } 227 | .ui-icon-bullet { background-position: -80px -144px; } 228 | .ui-icon-radio-off { background-position: -96px -144px; } 229 | .ui-icon-radio-on { background-position: -112px -144px; } 230 | .ui-icon-pin-w { background-position: -128px -144px; } 231 | .ui-icon-pin-s { background-position: -144px -144px; } 232 | .ui-icon-play { background-position: 0 -160px; } 233 | .ui-icon-pause { background-position: -16px -160px; } 234 | .ui-icon-seek-next { background-position: -32px -160px; } 235 | .ui-icon-seek-prev { background-position: -48px -160px; } 236 | .ui-icon-seek-end { background-position: -64px -160px; } 237 | .ui-icon-seek-start { background-position: -80px -160px; } 238 | /* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ 239 | .ui-icon-seek-first { background-position: -80px -160px; } 240 | .ui-icon-stop { background-position: -96px -160px; } 241 | .ui-icon-eject { background-position: -112px -160px; } 242 | .ui-icon-volume-off { background-position: -128px -160px; } 243 | .ui-icon-volume-on { background-position: -144px -160px; } 244 | .ui-icon-power { background-position: 0 -176px; } 245 | .ui-icon-signal-diag { background-position: -16px -176px; } 246 | .ui-icon-signal { background-position: -32px -176px; } 247 | .ui-icon-battery-0 { background-position: -48px -176px; } 248 | .ui-icon-battery-1 { background-position: -64px -176px; } 249 | .ui-icon-battery-2 { background-position: -80px -176px; } 250 | .ui-icon-battery-3 { background-position: -96px -176px; } 251 | .ui-icon-circle-plus { background-position: 0 -192px; } 252 | .ui-icon-circle-minus { background-position: -16px -192px; } 253 | .ui-icon-circle-close { background-position: -32px -192px; } 254 | .ui-icon-circle-triangle-e { background-position: -48px -192px; } 255 | .ui-icon-circle-triangle-s { background-position: -64px -192px; } 256 | .ui-icon-circle-triangle-w { background-position: -80px -192px; } 257 | .ui-icon-circle-triangle-n { background-position: -96px -192px; } 258 | .ui-icon-circle-arrow-e { background-position: -112px -192px; } 259 | .ui-icon-circle-arrow-s { background-position: -128px -192px; } 260 | .ui-icon-circle-arrow-w { background-position: -144px -192px; } 261 | .ui-icon-circle-arrow-n { background-position: -160px -192px; } 262 | .ui-icon-circle-zoomin { background-position: -176px -192px; } 263 | .ui-icon-circle-zoomout { background-position: -192px -192px; } 264 | .ui-icon-circle-check { background-position: -208px -192px; } 265 | .ui-icon-circlesmall-plus { background-position: 0 -208px; } 266 | .ui-icon-circlesmall-minus { background-position: -16px -208px; } 267 | .ui-icon-circlesmall-close { background-position: -32px -208px; } 268 | .ui-icon-squaresmall-plus { background-position: -48px -208px; } 269 | .ui-icon-squaresmall-minus { background-position: -64px -208px; } 270 | .ui-icon-squaresmall-close { background-position: -80px -208px; } 271 | .ui-icon-grip-dotted-vertical { background-position: 0 -224px; } 272 | .ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } 273 | .ui-icon-grip-solid-vertical { background-position: -32px -224px; } 274 | .ui-icon-grip-solid-horizontal { background-position: -48px -224px; } 275 | .ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } 276 | .ui-icon-grip-diagonal-se { background-position: -80px -224px; } 277 | 278 | 279 | /* Misc visuals 280 | ----------------------------------*/ 281 | 282 | /* Corner radius */ 283 | .ui-corner-tl { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; border-top-left-radius: 6px; } 284 | .ui-corner-tr { -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; border-top-right-radius: 6px; } 285 | .ui-corner-bl { -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; } 286 | .ui-corner-br { -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; } 287 | .ui-corner-top { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; border-top-left-radius: 6px; -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; border-top-right-radius: 6px; } 288 | .ui-corner-bottom { -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; } 289 | .ui-corner-right { -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; border-top-right-radius: 6px; -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; } 290 | .ui-corner-left { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; border-top-left-radius: 6px; -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; } 291 | .ui-corner-all { -moz-border-radius: 6px; -webkit-border-radius: 6px; border-radius: 6px; } 292 | 293 | /* Overlays */ 294 | .ui-widget-overlay { background: #5c5c5c url(images/ui-bg_flat_50_5c5c5c_40x100.png) 50% 50% repeat-x; opacity: .80;filter:Alpha(Opacity=80); } 295 | .ui-widget-shadow { margin: -7px 0 0 -7px; padding: 7px; background: #cccccc url(images/ui-bg_flat_30_cccccc_40x100.png) 50% 50% repeat-x; opacity: .60;filter:Alpha(Opacity=60); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/* 296 | * jQuery UI Resizable 1.8.6 297 | * 298 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 299 | * Dual licensed under the MIT or GPL Version 2 licenses. 300 | * http://jquery.org/license 301 | * 302 | * http://docs.jquery.com/UI/Resizable#theming 303 | */ 304 | .ui-resizable { position: relative;} 305 | .ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;} 306 | .ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } 307 | .ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } 308 | .ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } 309 | .ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } 310 | .ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } 311 | .ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } 312 | .ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } 313 | .ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } 314 | .ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* 315 | * jQuery UI Selectable 1.8.6 316 | * 317 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 318 | * Dual licensed under the MIT or GPL Version 2 licenses. 319 | * http://jquery.org/license 320 | * 321 | * http://docs.jquery.com/UI/Selectable#theming 322 | */ 323 | .ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } 324 | /* 325 | * jQuery UI Button 1.8.6 326 | * 327 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 328 | * Dual licensed under the MIT or GPL Version 2 licenses. 329 | * http://jquery.org/license 330 | * 331 | * http://docs.jquery.com/UI/Button#theming 332 | */ 333 | .ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ 334 | .ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ 335 | button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ 336 | .ui-button-icons-only { width: 3.4em; } 337 | button.ui-button-icons-only { width: 3.7em; } 338 | 339 | /*button text element */ 340 | .ui-button .ui-button-text { display: block; line-height: 1.4; } 341 | .ui-button-text-only .ui-button-text { padding: .4em 1em; } 342 | .ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } 343 | .ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } 344 | .ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } 345 | .ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } 346 | /* no icon support for input elements, provide padding by default */ 347 | input.ui-button { padding: .4em 1em; } 348 | 349 | /*button icon element(s) */ 350 | .ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } 351 | .ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } 352 | .ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } 353 | .ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } 354 | .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } 355 | 356 | /*button sets*/ 357 | .ui-buttonset { margin-right: 7px; } 358 | .ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } 359 | 360 | /* workarounds */ 361 | button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ 362 | /* 363 | * jQuery UI Dialog 1.8.6 364 | * 365 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 366 | * Dual licensed under the MIT or GPL Version 2 licenses. 367 | * http://jquery.org/license 368 | * 369 | * http://docs.jquery.com/UI/Dialog#theming 370 | */ 371 | .ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } 372 | .ui-dialog .ui-dialog-titlebar { padding: .5em 1em .3em; position: relative; } 373 | .ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .2em 0; } 374 | .ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } 375 | .ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } 376 | .ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } 377 | .ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } 378 | .ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } 379 | .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } 380 | .ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } 381 | .ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } 382 | .ui-draggable .ui-dialog-titlebar { cursor: move; } 383 | /* 384 | * jQuery UI Slider 1.8.6 385 | * 386 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 387 | * Dual licensed under the MIT or GPL Version 2 licenses. 388 | * http://jquery.org/license 389 | * 390 | * http://docs.jquery.com/UI/Slider#theming 391 | */ 392 | .ui-slider { position: relative; text-align: left; } 393 | .ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } 394 | .ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } 395 | 396 | .ui-slider-horizontal { height: .8em; } 397 | .ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } 398 | .ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } 399 | .ui-slider-horizontal .ui-slider-range-min { left: 0; } 400 | .ui-slider-horizontal .ui-slider-range-max { right: 0; } 401 | 402 | .ui-slider-vertical { width: .8em; height: 100px; } 403 | .ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } 404 | .ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } 405 | .ui-slider-vertical .ui-slider-range-min { bottom: 0; } 406 | .ui-slider-vertical .ui-slider-range-max { top: 0; } -------------------------------------------------------------------------------- /lib/asset/raphael-min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Raphael 1.5.2 - JavaScript Vector Library 3 | * 4 | * Copyright (c) 2010 Dmitry Baranovskiy (http://raphaeljs.com) 5 | * Licensed under the MIT (http://raphaeljs.com/license.html) license. 6 | */ 7 | (function(){function a(){if(a.is(arguments[0],G)){var b=arguments[0],d=bV[m](a,b.splice(0,3+a.is(b[0],E))),e=d.set();for(var g=0,h=b[w];g";bg=bf.firstChild;bg.style.behavior="url(#default#VML)";if(!(bg&&typeof bg.adj=="object"))return a.type=null;bf=null}a.svg=!(a.vml=a.type=="VML");j[e]=a[e];k=j[e];a._id=0;a._oid=0;a.fn={};a.is=function(a,b){b=x.call(b);if(b=="finite")return!O[f](+a);return b=="null"&&a===null||b==typeof a||b=="object"&&a===Object(a)||b=="array"&&Array.isArray&&Array.isArray(a)||J.call(a).slice(8,-1).toLowerCase()==b};a.angle=function(b,c,d,e,f,g){{if(f==null){var h=b-d,i=c-e;if(!h&&!i)return 0;return((h<0)*180+y.atan(-i/-h)*180/D+360)%360}return a.angle(b,c,f,g)-a.angle(d,e,f,g)}};a.rad=function(a){return a%360*D/180};a.deg=function(a){return a*180/D%360};a.snapTo=function(b,c,d){d=a.is(d,"finite")?d:10;if(a.is(b,G)){var e=b.length;while(e--)if(B(b[e]-c)<=d)return b[e]}else{b=+b;var f=c%b;if(fb-d)return c-f+b}return c};function bh(){var a=[],b=0;for(;b<32;b++)a[b]=(~(~(y.random()*16)))[H](16);a[12]=4;a[16]=(a[16]&3|8)[H](16);return"r-"+a[v]("")}a.setWindow=function(a){h=a;g=h.document};var bi=function(b){if(a.vml){var c=/^\s+|\s+$/g,d;try{var e=new ActiveXObject("htmlfile");e.write("");e.close();d=e.body}catch(a){d=createPopup().document.body}var f=d.createTextRange();bi=bm(function(a){try{d.style.color=r(a)[Y](c,p);var b=f.queryCommandValue("ForeColor");b=(b&255)<<16|b&65280|(b&16711680)>>>16;return"#"+("000000"+b[H](16)).slice(-6)}catch(a){return"none"}})}else{var h=g.createElement("i");h.title="Raphaël Colour Picker";h.style.display="none";g.body[l](h);bi=bm(function(a){h.style.color=a;return g.defaultView.getComputedStyle(h,p).getPropertyValue("color")})}return bi(b)},bj=function(){return"hsb("+[this.h,this.s,this.b]+")"},bk=function(){return"hsl("+[this.h,this.s,this.l]+")"},bl=function(){return this.hex};a.hsb2rgb=function(b,c,d,e){if(a.is(b,"object")&&"h"in b&&"s"in b&&"b"in b){d=b.b;c=b.s;b=b.h;e=b.o}return a.hsl2rgb(b,c,d/2,e)};a.hsl2rgb=function(b,c,d,e){if(a.is(b,"object")&&"h"in b&&"s"in b&&"l"in b){d=b.l;c=b.s;b=b.h}if(b>1||c>1||d>1){b/=360;c/=100;d/=100}var f={},g=["r","g","b"],h,i,j,k,l,m;if(c){d<0.5?h=d*(1+c):h=d+c-d*c;i=2*d-h;for(var n=0;n<3;n++){j=b+1/3*-(n-1);j<0&&j++;j>1&&j--;j*6<1?f[g[n]]=i+(h-i)*6*j:j*2<1?f[g[n]]=h:j*3<2?f[g[n]]=i+(h-i)*(2/3-j)*6:f[g[n]]=i}}else f={r:d,g:d,b:d};f.r*=255;f.g*=255;f.b*=255;f.hex="#"+(16777216|f.b|f.g<<8|f.r<<16).toString(16).slice(1);a.is(e,"finite")&&(f.opacity=e);f.toString=bl;return f};a.rgb2hsb=function(b,c,d){if(c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b){d=b.b;c=b.g;b=b.r}if(c==null&&a.is(b,F)){var e=a.getRGB(b);b=e.r;c=e.g;d=e.b}if(b>1||c>1||d>1){b/=255;c/=255;d/=255}var f=z(b,c,d),g=A(b,c,d),h,i,j=f;{if(g==f)return{h:0,s:0,b:f,toString:bj};var k=f-g;i=k/f;b==f?h=(c-d)/k:c==f?h=2+(d-b)/k:h=4+(b-c)/k;h/=6;h<0&&h++;h>1&&h--}return{h:h,s:i,b:j,toString:bj}};a.rgb2hsl=function(b,c,d){if(c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b){d=b.b;c=b.g;b=b.r}if(c==null&&a.is(b,F)){var e=a.getRGB(b);b=e.r;c=e.g;d=e.b}if(b>1||c>1||d>1){b/=255;c/=255;d/=255}var f=z(b,c,d),g=A(b,c,d),h,i,j=(f+g)/2,k;if(g==f)k={h:0,s:0,l:j};else{var l=f-g;i=j<0.5?l/(f+g):l/(2-f-g);b==f?h=(c-d)/l:c==f?h=2+(d-b)/l:h=4+(b-c)/l;h/=6;h<0&&h++;h>1&&h--;k={h:h,s:i,l:j}}k.toString=bk;return k};a._path2string=function(){return this.join(",")[Y](ba,"$1")};function bm(a,b,c){function d(){var g=Array[e].slice.call(arguments,0),h=g[v]("►"),i=d.cache=d.cache||{},j=d.count=d.count||[];if(i[f](h))return c?c(i[h]):i[h];j[w]>=1000&&delete i[j.shift()];j[L](h);i[h]=a[m](b,g);return c?c(i[h]):i[h]}return d}a.getRGB=bm(function(b){if(!b||!(!((b=r(b)).indexOf("-")+1)))return{r:-1,g:-1,b:-1,hex:"none",error:1};if(b=="none")return{r:-1,g:-1,b:-1,hex:"none"};!(_[f](b.toLowerCase().substring(0,2))||b.charAt()=="#")&&(b=bi(b));var c,d,e,g,h,i,j,k=b.match(N);if(k){if(k[2]){g=T(k[2].substring(5),16);e=T(k[2].substring(3,5),16);d=T(k[2].substring(1,3),16)}if(k[3]){g=T((i=k[3].charAt(3))+i,16);e=T((i=k[3].charAt(2))+i,16);d=T((i=k[3].charAt(1))+i,16)}if(k[4]){j=k[4][s]($);d=S(j[0]);j[0].slice(-1)=="%"&&(d*=2.55);e=S(j[1]);j[1].slice(-1)=="%"&&(e*=2.55);g=S(j[2]);j[2].slice(-1)=="%"&&(g*=2.55);k[1].toLowerCase().slice(0,4)=="rgba"&&(h=S(j[3]));j[3]&&j[3].slice(-1)=="%"&&(h/=100)}if(k[5]){j=k[5][s]($);d=S(j[0]);j[0].slice(-1)=="%"&&(d*=2.55);e=S(j[1]);j[1].slice(-1)=="%"&&(e*=2.55);g=S(j[2]);j[2].slice(-1)=="%"&&(g*=2.55);(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360);k[1].toLowerCase().slice(0,4)=="hsba"&&(h=S(j[3]));j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsb2rgb(d,e,g,h)}if(k[6]){j=k[6][s]($);d=S(j[0]);j[0].slice(-1)=="%"&&(d*=2.55);e=S(j[1]);j[1].slice(-1)=="%"&&(e*=2.55);g=S(j[2]);j[2].slice(-1)=="%"&&(g*=2.55);(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360);k[1].toLowerCase().slice(0,4)=="hsla"&&(h=S(j[3]));j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsl2rgb(d,e,g,h)}k={r:d,g:e,b:g};k.hex="#"+(16777216|g|e<<8|d<<16).toString(16).slice(1);a.is(h,"finite")&&(k.opacity=h);return k}return{r:-1,g:-1,b:-1,hex:"none",error:1}},a);a.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||0.75},c=this.hsb2rgb(b.h,b.s,b.b);b.h+=0.075;if(b.h>1){b.h=0;b.s-=0.2;b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b})}return c.hex};a.getColor.reset=function(){delete this.start};a.parsePathString=bm(function(b){if(!b)return null;var c={a:7,c:6,h:1,l:2,m:2,q:4,s:4,t:2,v:1,z:0},d=[];a.is(b,G)&&a.is(b[0],G)&&(d=bo(b));d[w]||r(b)[Y](bb,function(a,b,e){var f=[],g=x.call(b);e[Y](bc,function(a,b){b&&f[L](+b)});if(g=="m"&&f[w]>2){d[L]([b][n](f.splice(0,2)));g="l";b=b=="m"?"l":"L"}while(f[w]>=c[g]){d[L]([b][n](f.splice(0,c[g])));if(!c[g])break}});d[H]=a._path2string;return d});a.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=C(j,3)*a+C(j,2)*3*i*c+j*3*i*i*e+C(i,3)*g,l=C(j,3)*b+C(j,2)*3*i*d+j*3*i*i*f+C(i,3)*h,m=a+2*i*(c-a)+i*i*(e-2*c+a),n=b+2*i*(d-b)+i*i*(f-2*d+b),o=c+2*i*(e-c)+i*i*(g-2*e+c),p=d+2*i*(f-d)+i*i*(h-2*f+d),q=(1-i)*a+i*c,r=(1-i)*b+i*d,s=(1-i)*e+i*g,t=(1-i)*f+i*h,u=90-y.atan((m-o)/(n-p))*180/D;(m>o||n1){x=y.sqrt(x);c=x*c;d=x*d}var z=c*c,A=d*d,C=(f==g?-1:1)*y.sqrt(B((z*A-z*u*u-A*t*t)/(z*u*u+A*t*t))),E=C*c*u/d+(a+h)/2,F=C*-d*t/c+(b+i)/2,G=y.asin(((b-F)/d).toFixed(9)),H=y.asin(((i-F)/d).toFixed(9));G=aH&&(G=G-D*2);!g&&H>G&&(H=H-D*2)}var I=H-G;if(B(I)>k){var J=H,K=h,L=i;H=G+k*(g&&H>G?1:-1);h=E+c*y.cos(H);i=F+d*y.sin(H);m=bt(h,i,c,d,e,0,g,K,L,[H,J,E,F])}I=H-G;var M=y.cos(G),N=y.sin(G),O=y.cos(H),P=y.sin(H),Q=y.tan(I/4),R=4/3*c*Q,S=4/3*d*Q,T=[a,b],U=[a+R*N,b-S*M],V=[h+R*P,i-S*O],W=[h,i];U[0]=2*T[0]-U[0];U[1]=2*T[1]-U[1];{if(j)return[U,V,W][n](m);m=[U,V,W][n](m)[v]()[s](",");var X=[];for(var Y=0,Z=m[w];Y"1e12"&&(l=0.5);B(n)>"1e12"&&(n=0.5);if(l>0&&l<1){q=bu(a,b,c,d,e,f,g,h,l);p[L](q.x);o[L](q.y)}if(n>0&&n<1){q=bu(a,b,c,d,e,f,g,h,n);p[L](q.x);o[L](q.y)}i=f-2*d+b-(h-2*f+d);j=2*(d-b)-2*(f-d);k=b-d;l=(-j+y.sqrt(j*j-4*i*k))/2/i;n=(-j-y.sqrt(j*j-4*i*k))/2/i;B(l)>"1e12"&&(l=0.5);B(n)>"1e12"&&(n=0.5);if(l>0&&l<1){q=bu(a,b,c,d,e,f,g,h,l);p[L](q.x);o[L](q.y)}if(n>0&&n<1){q=bu(a,b,c,d,e,f,g,h,n);p[L](q.x);o[L](q.y)}return{min:{x:A[m](0,p),y:A[m](0,o)},max:{x:z[m](0,p),y:z[m](0,o)}}}),bw=bm(function(a,b){var c=bq(a),d=b&&bq(b),e={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g=function(a,b){var c,d;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null);switch(a[0]){case"M":b.X=a[1];b.Y=a[2];break;case"A":a=["C"][n](bt[m](0,[b.x,b.y][n](a.slice(1))));break;case"S":c=b.x+(b.x-(b.bx||b.x));d=b.y+(b.y-(b.by||b.y));a=["C",c,d][n](a.slice(1));break;case"T":b.qx=b.x+(b.x-(b.qx||b.x));b.qy=b.y+(b.y-(b.qy||b.y));a=["C"][n](bs(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1];b.qy=a[2];a=["C"][n](bs(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][n](br(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][n](br(b.x,b.y,a[1],b.y));break;case"V":a=["C"][n](br(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][n](br(b.x,b.y,b.X,b.Y));break}return a},h=function(a,b){if(a[b][w]>7){a[b].shift();var e=a[b];while(e[w])a.splice(b++,0,["C"][n](e.splice(0,6)));a.splice(b,1);k=z(c[w],d&&d[w]||0)}},i=function(a,b,e,f,g){if(a&&b&&a[g][0]=="M"&&b[g][0]!="M"){b.splice(g,0,["M",f.x,f.y]);e.bx=0;e.by=0;e.x=a[g][1];e.y=a[g][2];k=z(c[w],d&&d[w]||0)}};for(var j=0,k=z(c[w],d&&d[w]||0);j0.5)*2-1;C(e-0.5,2)+C(f-0.5,2)>0.25&&(f=y.sqrt(0.25-C(e-0.5,2))*g+0.5)&&f!=0.5&&(f=f.toFixed(5)-0.00001*g)}return p});b=b[s](/\s*\-\s*/);if(d=="linear"){var i=b.shift();i=-S(i);if(isNaN(i))return null;var j=[0,0,y.cos(i*D/180),y.sin(i*D/180)],k=1/(z(B(j[2]),B(j[3]))||1);j[2]*=k;j[3]*=k;if(j[2]<0){j[0]=-j[2];j[2]=0}if(j[3]<0){j[1]=-j[3];j[3]=0}}var m=bx(b);if(!m)return null;var n=a.getAttribute(I);n=n.match(/^url\(#(.*)\)$/);n&&c.defs.removeChild(g.getElementById(n[1]));var o=bG(d+"Gradient");o.id=bh();bG(o,d=="radial"?{fx:e,fy:f}:{x1:j[0],y1:j[1],x2:j[2],y2:j[3]});c.defs[l](o);for(var q=0,t=m[w];q1?G.opacity/100:G.opacity});case"stroke":G=a.getRGB(o);h[R](n,G.hex);n=="stroke"&&G[f]("opacity")&&bG(h,{"stroke-opacity":G.opacity>1?G.opacity/100:G.opacity});break;case"gradient":(({circle:1,ellipse:1})[f](c.type)||r(o).charAt()!="r")&&bI(h,o,c.paper);break;case"opacity":i.gradient&&!i[f]("stroke-opacity")&&bG(h,{"stroke-opacity":o>1?o/100:o});case"fill-opacity":if(i.gradient){var H=g.getElementById(h.getAttribute(I)[Y](/^url\(#|\)$/g,p));if(H){var J=H.getElementsByTagName("stop");J[J[w]-1][R]("stop-opacity",o)}break}default:n=="font-size"&&(o=T(o,10)+"px");var K=n[Y](/(\-.)/g,function(a){return V.call(a.substring(1))});h.style[K]=o;h[R](n,o);break}}}bM(c,d);m?c.rotate(m.join(q)):S(j)&&c.rotate(j,true)},bL=1.2,bM=function(b,c){if(b.type!="text"||!(c[f]("text")||c[f]("font")||c[f]("font-size")||c[f]("x")||c[f]("y")))return;var d=b.attrs,e=b.node,h=e.firstChild?T(g.defaultView.getComputedStyle(e.firstChild,p).getPropertyValue("font-size"),10):10;if(c[f]("text")){d.text=c.text;while(e.firstChild)e.removeChild(e.firstChild);var i=r(c.text)[s]("\n");for(var j=0,k=i[w];jb.height&&(b.height=e.y+e.height-b.y);e.x+e.width-b.x>b.width&&(b.width=e.x+e.width-b.x)}}a&&this.hide();return b};bN[e].attr=function(b,c){if(this.removed)return this;if(b==null){var d={};for(var e in this.attrs)this.attrs[f](e)&&(d[e]=this.attrs[e]);this._.rt.deg&&(d.rotation=this.rotate());(this._.sx!=1||this._.sy!=1)&&(d.scale=this.scale());d.gradient&&d.fill=="none"&&(d.fill=d.gradient)&&delete d.gradient;return d}if(c==null&&a.is(b,F)){if(b=="translation")return cz.call(this);if(b=="rotation")return this.rotate();if(b=="scale")return this.scale();if(b==I&&this.attrs.fill=="none"&&this.attrs.gradient)return this.attrs.gradient;return this.attrs[b]}if(c==null&&a.is(b,G)){var g={};for(var h=0,i=b.length;h"));m.W=h.w=m.paper.span.offsetWidth;m.H=h.h=m.paper.span.offsetHeight;m.X=h.x;m.Y=h.y+Q(m.H/2);switch(h["text-anchor"]){case"start":m.node.style["v-text-align"]="left";m.bbx=Q(m.W/2);break;case"end":m.node.style["v-text-align"]="right";m.bbx=-Q(m.W/2);break;default:m.node.style["v-text-align"]="center";break}}};bI=function(a,b){a.attrs=a.attrs||{};var c=a.attrs,d,e="linear",f=".5 .5";a.attrs.gradient=b;b=r(b)[Y](bd,function(a,b,c){e="radial";if(b&&c){b=S(b);c=S(c);C(b-0.5,2)+C(c-0.5,2)>0.25&&(c=y.sqrt(0.25-C(b-0.5,2))*((c>0.5)*2-1)+0.5);f=b+q+c}return p});b=b[s](/\s*\-\s*/);if(e=="linear"){var g=b.shift();g=-S(g);if(isNaN(g))return null}var h=bx(b);if(!h)return null;a=a.shape||a.node;d=a.getElementsByTagName(I)[0]||cd(I);!d.parentNode&&a.appendChild(d);if(h[w]){d.on=true;d.method="none";d.color=h[0].color;d.color2=h[h[w]-1].color;var i=[];for(var j=0,k=h[w];j")}}catch(a){cd=function(a){return g.createElement("<"+a+" xmlns=\"urn:schemas-microsoft.com:vml\" class=\"rvml\">")}}bV=function(){var b=by[m](0,arguments),c=b.container,d=b.height,e,f=b.width,h=b.x,i=b.y;if(!c)throw new Error("VML container not found.");var k=new j,n=k.canvas=g.createElement("div"),o=n.style;h=h||0;i=i||0;f=f||512;d=d||342;f==+f&&(f+="px");d==+d&&(d+="px");k.width=1000;k.height=1000;k.coordsize=b_*1000+q+b_*1000;k.coordorigin="0 0";k.span=g.createElement("span");k.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";n[l](k.span);o.cssText=a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",f,d);if(c==1){g.body[l](n);o.left=h+"px";o.top=i+"px";o.position="absolute"}else c.firstChild?c.insertBefore(n,c.firstChild):c[l](n);bz.call(k,k,a.fn);return k};k.clear=function(){this.canvas.innerHTML=p;this.span=g.createElement("span");this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";this.canvas[l](this.span);this.bottom=this.top=null};k.remove=function(){this.canvas.parentNode.removeChild(this.canvas);for(var a in this)this[a]=bF(a);return true}}var ce=navigator.userAgent.match(/Version\\x2f(.*?)\s/);navigator.vendor=="Apple Computer, Inc."&&(ce&&ce[1]<4||navigator.platform.slice(0,2)=="iP")?k.safari=function(){var a=this.rect(-99,-99,this.width+99,this.height+99).attr({stroke:"none"});h.setTimeout(function(){a.remove()})}:k.safari=function(){};var cf=function(){this.returnValue=false},cg=function(){return this.originalEvent.preventDefault()},ch=function(){this.cancelBubble=true},ci=function(){return this.originalEvent.stopPropagation()},cj=(function(){{if(g.addEventListener)return function(a,b,c,d){var e=o&&u[b]?u[b]:b,g=function(e){if(o&&u[f](b))for(var g=0,h=e.targetTouches&&e.targetTouches.length;g1&&(a=Array[e].splice.call(arguments,0,arguments[w]));return new cC(a)};k.setSize=bU;k.top=k.bottom=null;k.raphael=a;function co(){return this.x+q+this.y}bO.resetScale=function(){if(this.removed)return this;this._.sx=1;this._.sy=1;this.attrs.scale="1 1"};bO.scale=function(a,b,c,d){if(this.removed)return this;if(a==null&&b==null)return{x:this._.sx,y:this._.sy,toString:co};b=b||a;!(+b)&&(b=a);var e,f,g,h,i=this.attrs;if(a!=0){var j=this.getBBox(),k=j.x+j.width/2,l=j.y+j.height/2,m=B(a/this._.sx),o=B(b/this._.sy);c=+c||c==0?c:k;d=+d||d==0?d:l;var r=this._.sx>0,s=this._.sy>0,t=~(~(a/B(a))),u=~(~(b/B(b))),x=m*t,y=o*u,z=this.node.style,A=c+B(k-c)*x*(k>c==r?1:-1),C=d+B(l-d)*y*(l>d==s?1:-1),D=a*t>b*u?o:m;switch(this.type){case"rect":case"image":var E=i.width*m,F=i.height*o;this.attr({height:F,r:i.r*D,width:E,x:A-E/2,y:C-F/2});break;case"circle":case"ellipse":this.attr({rx:i.rx*m,ry:i.ry*o,r:i.r*D,cx:A,cy:C});break;case"text":this.attr({x:A,y:C});break;case"path":var G=bp(i.path),H=true,I=r?x:m,J=s?y:o;for(var K=0,L=G[w];Kr)p=n.data[r*l];else{p=a.findDotsAtSegment(b,c,d,e,f,g,h,i,r/l);n.data[r]=p}r&&(k+=C(C(o.x-p.x,2)+C(o.y-p.y,2),0.5));if(j!=null&&k>=j)return p;o=p}if(j==null)return k},cr=function(b,c){return function(d,e,f){d=bw(d);var g,h,i,j,k="",l={},m,n=0;for(var o=0,p=d.length;oe){if(c&&!l.start){m=cq(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);k+=["C",m.start.x,m.start.y,m.m.x,m.m.y,m.x,m.y];if(f)return k;l.start=k;k=["M",m.x,m.y+"C",m.n.x,m.n.y,m.end.x,m.end.y,i[5],i[6]][v]();n+=j;g=+i[5];h=+i[6];continue}if(!b&&!c){m=cq(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);return{x:m.x,y:m.y,alpha:m.alpha}}}n+=j;g=+i[5];h=+i[6]}k+=i}l.end=k;m=b?n:c?l:a.findDotsAtSegment(g,h,i[1],i[2],i[3],i[4],i[5],i[6],1);m.alpha&&(m={x:m.x,y:m.y,alpha:m.alpha});return m}},cs=cr(1),ct=cr(),cu=cr(0,1);bO.getTotalLength=function(){if(this.type!="path")return;if(this.node.getTotalLength)return this.node.getTotalLength();return cs(this.attrs.path)};bO.getPointAtLength=function(a){if(this.type!="path")return;return ct(this.attrs.path,a)};bO.getSubpath=function(a,b){if(this.type!="path")return;if(B(this.getTotalLength()-b)<"1e-6")return cu(this.attrs.path,a).end;var c=cu(this.attrs.path,b,1);return a?cu(c,a).end:c};a.easing_formulas={linear:function(a){return a},"<":function(a){return C(a,3)},">":function(a){return C(a-1,3)+1},"<>":function(a){a=a*2;if(a<1)return C(a,3)/2;a-=2;return(C(a,3)+2)/2},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a=a-1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){if(a==0||a==1)return a;var b=0.3,c=b/4;return C(2,-10*a)*y.sin((a-c)*(2*D)/b)+1},bounce:function(a){var b=7.5625,c=2.75,d;if(a<1/c)d=b*a*a;else if(a<2/c){a-=1.5/c;d=b*a*a+0.75}else if(a<2.5/c){a-=2.25/c;d=b*a*a+0.9375}else{a-=2.625/c;d=b*a*a+0.984375}return d}};var cv=[],cw=function(){var b=+(new Date);for(var c=0;cd)return d;while(cf?c=e:d=e;e=(d-c)/2+c}return e}return n(a,1/(200*f))}bO.onAnimation=function(a){this._run=a||0;return this};bO.animate=function(c,d,e,g){var h=this;h.timeouts=h.timeouts||[];if(a.is(e,"function")||!e)g=e||null;if(h.removed){g&&g.call(h);return h}var i={},j={},k=false,l={};for(var m in c)if(c[f](m)){if(X[f](m)||h.paper.customAttributes[f](m)){k=true;i[m]=h.attr(m);i[m]==null&&(i[m]=W[m]);j[m]=c[m];switch(X[m]){case"along":var n=cs(c[m]),o=ct(c[m],n*!(!c.back)),p=h.getBBox();l[m]=n/d;l.tx=p.x;l.ty=p.y;l.sx=o.x;l.sy=o.y;j.rot=c.rot;j.back=c.back;j.len=n;c.rot&&(l.r=S(h.rotate())||0);break;case E:l[m]=(j[m]-i[m])/d;break;case"colour":i[m]=a.getRGB(i[m]);var q=a.getRGB(j[m]);l[m]={r:(q.r-i[m].r)/d,g:(q.g-i[m].g)/d,b:(q.b-i[m].b)/d};break;case"path":var t=bw(i[m],j[m]);i[m]=t[0];var u=t[1];l[m]=[];for(var v=0,x=i[m][w];v 0) { 71 | response.writeHead(200, {'content-type': 'application/json'}) 72 | response.end(JSON.stringify(htracr.capture)) 73 | } else { 74 | response.writeHead(204, {'content-type': 'text/plain'}) 75 | response.end() 76 | } 77 | break 78 | case 'servers': 79 | response.writeHead(200, { 80 | 'Content-Type': 'application/json' 81 | }) 82 | response.end(JSON.stringify(htracr.server_names)) 83 | break 84 | case 'packet': 85 | var packet_id = path_segs.shift() 86 | if (htracr.packets[packet_id]) { 87 | response.writeHead(200, { 88 | 'Content-Type': 'application/json' 89 | }) 90 | response.end(JSON.stringify({data: htracr.packets[packet_id]})) 91 | } else { 92 | response.writeHead(204, { 93 | 'Content-Type': 'text/plain' 94 | }) 95 | response.end() 96 | } 97 | break 98 | default: 99 | if (seg == 'images') { 100 | seg = path_segs.shift() 101 | } 102 | if (seg in assets) { 103 | response.writeHead(200, { 104 | 'Content-Type': assets[seg][1], 105 | 'Cache-Control': "max-age=7200" 106 | }) 107 | response.end(assets[seg][0]) 108 | } else { 109 | response.writeHead(404, {'Content-Type': "text/html"}) 110 | response.end("

Not Found

") 111 | } 112 | break 113 | } 114 | } 115 | 116 | exports.start = function(port, htracr) { 117 | http.createServer(function (request, response) { 118 | // special case for uploads 119 | if (request.url == "/upload" && request.method == "POST") { 120 | htracr.clear() 121 | var form = new formidable.IncomingForm() 122 | form.parse(request, function(e, fields, files) { 123 | if (e) { 124 | console.log("Incoming form error: " + e) 125 | response.writeHead(500, {'content-type': 'text/plain'}) 126 | htracr.err = e 127 | response.end() 128 | return 129 | } 130 | try { 131 | htracr.load_file(files.pcap.path) 132 | } catch (e) { 133 | console.log("Upload processing error: " + e) 134 | htracr.err = e 135 | response.end() 136 | return 137 | } 138 | response.writeHead(303, { 139 | 'content-type': 'text/plain', 140 | 'location': '/' 141 | }) 142 | response.end('received upload.') 143 | }) 144 | } else { // everything else 145 | request.input_buffer = "" 146 | request.on('data', function(chunk) { 147 | request.input_buffer += chunk 148 | }) 149 | request.on('end', function() { 150 | req_done(request, response, htracr) 151 | }) 152 | } 153 | 154 | }).listen(port) 155 | console.log('Server running on port ' + port + '.') 156 | } 157 | 158 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { "name" : "htracr" 2 | , "description" : "HTTP sniffing and low-level visualisation" 3 | , "keywords" : [ "sniffer", "HTTP", "trace" ] 4 | , "version" : "0.2.2" 5 | , "homepage" : "https://github.com/mnot/htracr" 6 | , "author" : "Mark Nottingham (http://www.mnot.net/)" 7 | , "contributors" : 8 | [ "zomo (http://www.zomo.co.uk)" 9 | ] 10 | , "repository" : 11 | { "type" : "git" 12 | , "url" : "http://github.com/mnot/htracr.git" 13 | } 14 | , "bugs" : 15 | { "web" : "http://github.com/mnot/htracr/issues" 16 | } 17 | , "main" : "lib/index.js" 18 | , "bin" : { "htracr" : "./htracr.js" } 19 | , "engines" : 20 | { "node" : ">=0.4.0" 21 | , "npm" : ">=0.2.18" 22 | } 23 | , "dependencies" : 24 | { "pcap" : ">=0.2.8 <=1.2.0" 25 | , "optimist" : ">=0.1.5" 26 | , "formidable" : ">=0.9.11" 27 | } 28 | , "licenses" : 29 | [ { "type" : "MIT" 30 | , "url" : "http://github.com/mnot/htracr/raw/master/LICENSE" 31 | } 32 | ] 33 | } 34 | --------------------------------------------------------------------------------