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&.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.is_a?(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.is_a?(k)}enddefinspected?(string)# Ensure string has valid encoding before pattern matching# to avoid ArgumentError with invalid byte sequencesstring=valid_encoding(string)unlessvalid_encoding?(string)string=~/#<.*>/rescue# If any encoding error occurs, assume it's not inspectedfalseendendendend