require'set'require'honeybadger/conversions'moduleHoneybadgermoduleUtil# Sanitizer sanitizes data for sending to Honeybadger's API. The filters# are based on Rails' HTTP parameter filter.classSanitizerCOOKIE_PAIRS=/[;,]\s?/COOKIE_SEP='='.freezeCOOKIE_PAIR_SEP='; '.freezeENCODE_OPTS={invalid: :replace,undef: :replace,replace: '?'.freeze}.freezeBASIC_OBJECT='#<BasicObject>'.freezeDEPTH='[DEPTH]'.freezeFILTERED='[FILTERED]'.freezeRAISED='[RAISED]'.freezeRECURSION='[RECURSION]'.freezeTRUNCATED='[TRUNCATED]'.freezeIMMUTABLE=[NilClass,FalseClass,TrueClass,Symbol,Numeric,Method].freezeMAX_STRING_SIZE=65536VALID_ENCODINGS=[Encoding::UTF_8,Encoding::ISO_8859_1].freezedefself.sanitize(data)@sanitizer||=new@sanitizer.sanitize(data)enddefinitialize(max_depth: 20,filters: [])@filters=!filters.empty?@max_depth=max_depthstrings,@regexps,@blocks=[],[],[]filters.eachdo|item|caseitemwhenProc@blocks<<itemwhenRegexp@regexps<<itemelsestrings<<Regexp.escape(item.to_s)endend@deep_regexps,@regexps=@regexps.partition{|r|r.to_s.include?('\\.'.freeze)}deep_strings,@strings=strings.partition{|s|s.include?('\\.'.freeze)}@regexps<<Regexp.new(strings.join('|'.freeze),true)unlessstrings.empty?@deep_regexps<<Regexp.new(deep_strings.join('|'.freeze),true)unlessdeep_strings.empty?enddefsanitize(data,depth=0,stack=nil,parents=[])returnBASIC_OBJECTifbasic_object?(data)ifrecursive?(data)returnRECURSIONifstack&&stack.include?(data.object_id)stack=stack?stack.dup:Set.newstack<<data.object_idendcasedatawhenHashreturnDEPTHifdepth>=max_depthhash=data.to_hashnew_hash={}hash.each_pairdo|key,value|parents.push(key)ifdeep_regexpskey=key.kind_of?(Symbol)?key:sanitize(key,depth+1,stack,parents)iffilter_key?(key,parents)new_hash[key]=FILTEREDelsevalue=sanitize(value,depth+1,stack,parents)ifblocks.any?&&!recursive?(value)key=key.dupifcan_dup?(key)value=value.dupifcan_dup?(value)blocks.each{|b|b.call(key,value)}endnew_hash[key]=valueendparents.popifdeep_regexpsendnew_hashwhenArray,SetreturnDEPTHifdepth>=max_depthdata.to_a.mapdo|value|sanitize(value,depth+1,stack,parents)endwhenNumeric,TrueClass,FalseClass,NilClassdatawhenStringsanitize_string(data)when->(d){d.respond_to?(:to_honeybadger)}returnDEPTHifdepth>=max_depthbegindata=data.to_honeybadgerrescuereturnRAISEDendsanitize(data,depth+1,stack,parents)else# all other objectsklass=data.classbegindata=String(data)rescuereturnRAISEDendreturn"#<#{klass.name}>"ifinspected?(data)sanitize_string(data)endenddeffilter_cookies(raw_cookies)returnraw_cookiesunlessfilters?cookies=[]raw_cookies.to_s.split(COOKIE_PAIRS).eachdo|pair|name,values=pair.split(COOKIE_SEP,2)values=FILTEREDiffilter_key?(name)cookies<<"#{name}=#{values}"endcookies.join(COOKIE_PAIR_SEP)enddeffilter_url(url)returnurlunlessfilters?filtered_url=url.to_s.dupfiltered_url.scan(/(?:^|&|\?)([^=?&]+)=([^&]+)/).eachdo|m|nextunlessfilter_key?(m[0])filtered_url.gsub!(/#{Regexp.escape(m[1])}/,FILTERED)endfiltered_urlendprivateattr_reader:max_depth,:regexps,:deep_regexps,:blocksdeffilters?!!@filtersenddeffilter_key?(key,parents=nil)returnfalseunlessfilters?returntrueifkey.respond_to?(:=~)&®exps.any?{|r|key=~r}returntrueifdeep_regexps&&parents&&(joined=parents.join("."))&&deep_regexps.any?{|r|joined=~r}falseenddefsanitize_string(string)string=valid_encoding(string)returnstringunlessstring.respond_to?(:size)&&string.size>MAX_STRING_SIZEstring[0...MAX_STRING_SIZE]+TRUNCATEDenddefvalid_encoding?(string)string.valid_encoding?&&(VALID_ENCODINGS.include?(string.encoding)||VALID_ENCODINGS.include?(Encoding.compatible?(''.freeze,string)))enddefvalid_encoding(string)returnstringifvalid_encoding?(string)string.encode(Encoding::UTF_8,**ENCODE_OPTS)enddefrecursive?(data)data.is_a?(Hash)||data.is_a?(Array)||data.is_a?(Set)||data.respond_to?(:to_honeybadger)enddefbasic_object?(object)object.respond_to?(:to_s)falserescue# BasicObject doesn't respond to `#respond_to?`.trueenddefcan_dup?(obj)!IMMUTABLE.any?{|k|obj.kind_of?(k)}enddefinspected?(string)String(string)=~/#<.*>/endendendend